1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 2 (function(win) { 3 var whiteSpaceRe = /^\s*|\s*$/g, 4 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 5 6 var tinymce = { 7 majorVersion : '3', 8 9 minorVersion : '5.4.1', 10 11 releaseDate : '2012-06-24', 12 13 _init : function() { 14 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 15 16 t.isOpera = win.opera && opera.buildNumber; 17 18 t.isWebKit = /WebKit/.test(ua); 19 20 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); 21 22 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 23 24 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 25 26 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 27 28 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 29 30 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 31 32 t.isMac = ua.indexOf('Mac') != -1; 33 34 t.isAir = /adobeair/i.test(ua); 35 36 t.isIDevice = /(iPad|iPhone)/.test(ua); 37 38 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 39 40 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 41 if (win.tinyMCEPreInit) { 42 t.suffix = tinyMCEPreInit.suffix; 43 t.baseURL = tinyMCEPreInit.base; 44 t.query = tinyMCEPreInit.query; 45 return; 46 } 47 48 // Get suffix and base 49 t.suffix = ''; 50 51 // If base element found, add that infront of baseURL 52 nl = d.getElementsByTagName('base'); 53 for (i=0; i<nl.length; i++) { 54 v = nl[i].href; 55 if (v) { 56 // Host only value like http://site.com or http://site.com:8008 57 if (/^https?:\/\/[^\/]+$/.test(v)) 58 v += '/'; 59 60 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 61 } 62 } 63 64 function getBase(n) { 65 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 66 if (/_(src|dev)\.js/g.test(n.src)) 67 t.suffix = '_src'; 68 69 if ((p = n.src.indexOf('?')) != -1) 70 t.query = n.src.substring(p + 1); 71 72 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 73 74 // If path to script is relative and a base href was found add that one infront 75 // the src property will always be an absolute one on non IE browsers and IE 8 76 // so this logic will basically only be executed on older IE versions 77 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 78 t.baseURL = base + t.baseURL; 79 80 return t.baseURL; 81 } 82 83 return null; 84 }; 85 86 // Check document 87 nl = d.getElementsByTagName('script'); 88 for (i=0; i<nl.length; i++) { 89 if (getBase(nl[i])) 90 return; 91 } 92 93 // Check head 94 n = d.getElementsByTagName('head')[0]; 95 if (n) { 96 nl = n.getElementsByTagName('script'); 97 for (i=0; i<nl.length; i++) { 98 if (getBase(nl[i])) 99 return; 100 } 101 } 102 103 return; 104 }, 105 106 is : function(o, t) { 107 if (!t) 108 return o !== undef; 109 110 if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) 111 return true; 112 113 return typeof(o) == t; 114 }, 115 116 makeMap : function(items, delim, map) { 117 var i; 118 119 items = items || []; 120 delim = delim || ','; 121 122 if (typeof(items) == "string") 123 items = items.split(delim); 124 125 map = map || {}; 126 127 i = items.length; 128 while (i--) 129 map[items[i]] = {}; 130 131 return map; 132 }, 133 134 each : function(o, cb, s) { 135 var n, l; 136 137 if (!o) 138 return 0; 139 140 s = s || o; 141 142 if (o.length !== undef) { 143 // Indexed arrays, needed for Safari 144 for (n=0, l = o.length; n < l; n++) { 145 if (cb.call(s, o[n], n, o) === false) 146 return 0; 147 } 148 } else { 149 // Hashtables 150 for (n in o) { 151 if (o.hasOwnProperty(n)) { 152 if (cb.call(s, o[n], n, o) === false) 153 return 0; 154 } 155 } 156 } 157 158 return 1; 159 }, 160 161 162 map : function(a, f) { 163 var o = []; 164 165 tinymce.each(a, function(v) { 166 o.push(f(v)); 167 }); 168 169 return o; 170 }, 171 172 grep : function(a, f) { 173 var o = []; 174 175 tinymce.each(a, function(v) { 176 if (!f || f(v)) 177 o.push(v); 178 }); 179 180 return o; 181 }, 182 183 inArray : function(a, v) { 184 var i, l; 185 186 if (a) { 187 for (i = 0, l = a.length; i < l; i++) { 188 if (a[i] === v) 189 return i; 190 } 191 } 192 193 return -1; 194 }, 195 196 extend : function(obj, ext) { 197 var i, l, name, args = arguments, value; 198 199 for (i = 1, l = args.length; i < l; i++) { 200 ext = args[i]; 201 for (name in ext) { 202 if (ext.hasOwnProperty(name)) { 203 value = ext[name]; 204 205 if (value !== undef) { 206 obj[name] = value; 207 } 208 } 209 } 210 } 211 212 return obj; 213 }, 214 215 216 trim : function(s) { 217 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 218 }, 219 220 create : function(s, p, root) { 221 var t = this, sp, ns, cn, scn, c, de = 0; 222 223 // Parse : <prefix> <class>:<super class> 224 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 225 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 226 227 // Create namespace for new class 228 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 229 230 // Class already exists 231 if (ns[cn]) 232 return; 233 234 // Make pure static class 235 if (s[2] == 'static') { 236 ns[cn] = p; 237 238 if (this.onCreate) 239 this.onCreate(s[2], s[3], ns[cn]); 240 241 return; 242 } 243 244 // Create default constructor 245 if (!p[cn]) { 246 p[cn] = function() {}; 247 de = 1; 248 } 249 250 // Add constructor and methods 251 ns[cn] = p[cn]; 252 t.extend(ns[cn].prototype, p); 253 254 // Extend 255 if (s[5]) { 256 sp = t.resolve(s[5]).prototype; 257 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 258 259 // Extend constructor 260 c = ns[cn]; 261 if (de) { 262 // Add passthrough constructor 263 ns[cn] = function() { 264 return sp[scn].apply(this, arguments); 265 }; 266 } else { 267 // Add inherit constructor 268 ns[cn] = function() { 269 this.parent = sp[scn]; 270 return c.apply(this, arguments); 271 }; 272 } 273 ns[cn].prototype[cn] = ns[cn]; 274 275 // Add super methods 276 t.each(sp, function(f, n) { 277 ns[cn].prototype[n] = sp[n]; 278 }); 279 280 // Add overridden methods 281 t.each(p, function(f, n) { 282 // Extend methods if needed 283 if (sp[n]) { 284 ns[cn].prototype[n] = function() { 285 this.parent = sp[n]; 286 return f.apply(this, arguments); 287 }; 288 } else { 289 if (n != cn) 290 ns[cn].prototype[n] = f; 291 } 292 }); 293 } 294 295 // Add static methods 296 t.each(p['static'], function(f, n) { 297 ns[cn][n] = f; 298 }); 299 300 if (this.onCreate) 301 this.onCreate(s[2], s[3], ns[cn].prototype); 302 }, 303 304 walk : function(o, f, n, s) { 305 s = s || this; 306 307 if (o) { 308 if (n) 309 o = o[n]; 310 311 tinymce.each(o, function(o, i) { 312 if (f.call(s, o, i, n) === false) 313 return false; 314 315 tinymce.walk(o, f, n, s); 316 }); 317 } 318 }, 319 320 createNS : function(n, o) { 321 var i, v; 322 323 o = o || win; 324 325 n = n.split('.'); 326 for (i=0; i<n.length; i++) { 327 v = n[i]; 328 329 if (!o[v]) 330 o[v] = {}; 331 332 o = o[v]; 333 } 334 335 return o; 336 }, 337 338 resolve : function(n, o) { 339 var i, l; 340 341 o = o || win; 342 343 n = n.split('.'); 344 for (i = 0, l = n.length; i < l; i++) { 345 o = o[n[i]]; 346 347 if (!o) 348 break; 349 } 350 351 return o; 352 }, 353 354 addUnload : function(f, s) { 355 var t = this, unload; 356 357 unload = function() { 358 var li = t.unloads, o, n; 359 360 if (li) { 361 // Call unload handlers 362 for (n in li) { 363 o = li[n]; 364 365 if (o && o.func) 366 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 367 } 368 369 // Detach unload function 370 if (win.detachEvent) { 371 win.detachEvent('onbeforeunload', fakeUnload); 372 win.detachEvent('onunload', unload); 373 } else if (win.removeEventListener) 374 win.removeEventListener('unload', unload, false); 375 376 // Destroy references 377 t.unloads = o = li = w = unload = 0; 378 379 // Run garbarge collector on IE 380 if (win.CollectGarbage) 381 CollectGarbage(); 382 } 383 }; 384 385 function fakeUnload() { 386 var d = document; 387 388 function stop() { 389 // Prevent memory leak 390 d.detachEvent('onstop', stop); 391 392 // Call unload handler 393 if (unload) 394 unload(); 395 396 d = 0; 397 }; 398 399 // Is there things still loading, then do some magic 400 if (d.readyState == 'interactive') { 401 // Fire unload when the currently loading page is stopped 402 if (d) 403 d.attachEvent('onstop', stop); 404 405 // Remove onstop listener after a while to prevent the unload function 406 // to execute if the user presses cancel in an onbeforeunload 407 // confirm dialog and then presses the browser stop button 408 win.setTimeout(function() { 409 if (d) 410 d.detachEvent('onstop', stop); 411 }, 0); 412 } 413 }; 414 415 f = {func : f, scope : s || this}; 416 417 if (!t.unloads) { 418 // Attach unload handler 419 if (win.attachEvent) { 420 win.attachEvent('onunload', unload); 421 win.attachEvent('onbeforeunload', fakeUnload); 422 } else if (win.addEventListener) 423 win.addEventListener('unload', unload, false); 424 425 // Setup initial unload handler array 426 t.unloads = [f]; 427 } else 428 t.unloads.push(f); 429 430 return f; 431 }, 432 433 removeUnload : function(f) { 434 var u = this.unloads, r = null; 435 436 tinymce.each(u, function(o, i) { 437 if (o && o.func == f) { 438 u.splice(i, 1); 439 r = f; 440 return false; 441 } 442 }); 443 444 return r; 445 }, 446 447 explode : function(s, d) { 448 if (!s || tinymce.is(s, 'array')) { 449 return s; 450 } 451 452 return tinymce.map(s.split(d || ','), tinymce.trim); 453 }, 454 455 _addVer : function(u) { 456 var v; 457 458 if (!this.query) 459 return u; 460 461 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 462 463 if (u.indexOf('#') == -1) 464 return u + v; 465 466 return u.replace('#', v + '#'); 467 }, 468 469 // Fix function for IE 9 where regexps isn't working correctly 470 // Todo: remove me once MS fixes the bug 471 _replace : function(find, replace, str) { 472 // On IE9 we have to fake $x replacement 473 if (isRegExpBroken) { 474 return str.replace(find, function() { 475 var val = replace, args = arguments, i; 476 477 for (i = 0; i < args.length - 2; i++) { 478 if (args[i] === undef) { 479 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 480 } else { 481 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 482 } 483 } 484 485 return val; 486 }); 487 } 488 489 return str.replace(find, replace); 490 } 491 492 }; 493 494 // Initialize the API 495 tinymce._init(); 496 497 // Expose tinymce namespace to the global namespace (window) 498 win.tinymce = win.tinyMCE = tinymce; 499 500 // Describe the different namespaces 501 502 })(window); 503 504 505 506 tinymce.create('tinymce.util.Dispatcher', { 507 scope : null, 508 listeners : null, 509 inDispatch: false, 510 511 Dispatcher : function(scope) { 512 this.scope = scope || this; 513 this.listeners = []; 514 }, 515 516 add : function(callback, scope) { 517 this.listeners.push({cb : callback, scope : scope || this.scope}); 518 519 return callback; 520 }, 521 522 addToTop : function(callback, scope) { 523 var self = this, listener = {cb : callback, scope : scope || self.scope}; 524 525 // Create new listeners if addToTop is executed in a dispatch loop 526 if (self.inDispatch) { 527 self.listeners = [listener].concat(self.listeners); 528 } else { 529 self.listeners.unshift(listener); 530 } 531 532 return callback; 533 }, 534 535 remove : function(callback) { 536 var listeners = this.listeners, output = null; 537 538 tinymce.each(listeners, function(listener, i) { 539 if (callback == listener.cb) { 540 output = listener; 541 listeners.splice(i, 1); 542 return false; 543 } 544 }); 545 546 return output; 547 }, 548 549 dispatch : function() { 550 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 551 552 self.inDispatch = true; 553 554 // Needs to be a real loop since the listener count might change while looping 555 // And this is also more efficient 556 for (i = 0; i < listeners.length; i++) { 557 listener = listeners[i]; 558 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 559 560 if (returnValue === false) 561 break; 562 } 563 564 self.inDispatch = false; 565 566 return returnValue; 567 } 568 569 }); 570 571 (function() { 572 var each = tinymce.each; 573 574 tinymce.create('tinymce.util.URI', { 575 URI : function(u, s) { 576 var t = this, o, a, b, base_url; 577 578 // Trim whitespace 579 u = tinymce.trim(u); 580 581 // Default settings 582 s = t.settings = s || {}; 583 584 // Strange app protocol that isn't http/https or local anchor 585 // For example: mailto,skype,tel etc. 586 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 587 t.source = u; 588 return; 589 } 590 591 // Absolute path with no host, fake host and protocol 592 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 593 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 594 595 // Relative path http:// or protocol relative //path 596 if (!/^[\w\-]*:?\/\//.test(u)) { 597 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 598 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 599 } 600 601 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 602 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 603 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 604 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 605 var s = u[i]; 606 607 // Zope 3 workaround, they use @@something 608 if (s) 609 s = s.replace(/\(mce_at\)/g, '@@'); 610 611 t[v] = s; 612 }); 613 614 b = s.base_uri; 615 if (b) { 616 if (!t.protocol) 617 t.protocol = b.protocol; 618 619 if (!t.userInfo) 620 t.userInfo = b.userInfo; 621 622 if (!t.port && t.host === 'mce_host') 623 t.port = b.port; 624 625 if (!t.host || t.host === 'mce_host') 626 t.host = b.host; 627 628 t.source = ''; 629 } 630 631 //t.path = t.path || '/'; 632 }, 633 634 setPath : function(p) { 635 var t = this; 636 637 p = /^(.*?)\/?(\w+)?$/.exec(p); 638 639 // Update path parts 640 t.path = p[0]; 641 t.directory = p[1]; 642 t.file = p[2]; 643 644 // Rebuild source 645 t.source = ''; 646 t.getURI(); 647 }, 648 649 toRelative : function(u) { 650 var t = this, o; 651 652 if (u === "./") 653 return u; 654 655 u = new tinymce.util.URI(u, {base_uri : t}); 656 657 // Not on same domain/port or protocol 658 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 659 return u.getURI(); 660 661 var tu = t.getURI(), uu = u.getURI(); 662 663 // Allow usage of the base_uri when relative_urls = true 664 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 665 return tu; 666 667 o = t.toRelPath(t.path, u.path); 668 669 // Add query 670 if (u.query) 671 o += '?' + u.query; 672 673 // Add anchor 674 if (u.anchor) 675 o += '#' + u.anchor; 676 677 return o; 678 }, 679 680 toAbsolute : function(u, nh) { 681 u = new tinymce.util.URI(u, {base_uri : this}); 682 683 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 684 }, 685 686 toRelPath : function(base, path) { 687 var items, bp = 0, out = '', i, l; 688 689 // Split the paths 690 base = base.substring(0, base.lastIndexOf('/')); 691 base = base.split('/'); 692 items = path.split('/'); 693 694 if (base.length >= items.length) { 695 for (i = 0, l = base.length; i < l; i++) { 696 if (i >= items.length || base[i] != items[i]) { 697 bp = i + 1; 698 break; 699 } 700 } 701 } 702 703 if (base.length < items.length) { 704 for (i = 0, l = items.length; i < l; i++) { 705 if (i >= base.length || base[i] != items[i]) { 706 bp = i + 1; 707 break; 708 } 709 } 710 } 711 712 if (bp === 1) 713 return path; 714 715 for (i = 0, l = base.length - (bp - 1); i < l; i++) 716 out += "../"; 717 718 for (i = bp - 1, l = items.length; i < l; i++) { 719 if (i != bp - 1) 720 out += "/" + items[i]; 721 else 722 out += items[i]; 723 } 724 725 return out; 726 }, 727 728 toAbsPath : function(base, path) { 729 var i, nb = 0, o = [], tr, outPath; 730 731 // Split paths 732 tr = /\/$/.test(path) ? '/' : ''; 733 base = base.split('/'); 734 path = path.split('/'); 735 736 // Remove empty chunks 737 each(base, function(k) { 738 if (k) 739 o.push(k); 740 }); 741 742 base = o; 743 744 // Merge relURLParts chunks 745 for (i = path.length - 1, o = []; i >= 0; i--) { 746 // Ignore empty or . 747 if (path[i].length === 0 || path[i] === ".") 748 continue; 749 750 // Is parent 751 if (path[i] === '..') { 752 nb++; 753 continue; 754 } 755 756 // Move up 757 if (nb > 0) { 758 nb--; 759 continue; 760 } 761 762 o.push(path[i]); 763 } 764 765 i = base.length - nb; 766 767 // If /a/b/c or / 768 if (i <= 0) 769 outPath = o.reverse().join('/'); 770 else 771 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 772 773 // Add front / if it's needed 774 if (outPath.indexOf('/') !== 0) 775 outPath = '/' + outPath; 776 777 // Add traling / if it's needed 778 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 779 outPath += tr; 780 781 return outPath; 782 }, 783 784 getURI : function(nh) { 785 var s, t = this; 786 787 // Rebuild source 788 if (!t.source || nh) { 789 s = ''; 790 791 if (!nh) { 792 if (t.protocol) 793 s += t.protocol + '://'; 794 795 if (t.userInfo) 796 s += t.userInfo + '@'; 797 798 if (t.host) 799 s += t.host; 800 801 if (t.port) 802 s += ':' + t.port; 803 } 804 805 if (t.path) 806 s += t.path; 807 808 if (t.query) 809 s += '?' + t.query; 810 811 if (t.anchor) 812 s += '#' + t.anchor; 813 814 t.source = s; 815 } 816 817 return t.source; 818 } 819 }); 820 })(); 821 822 (function() { 823 var each = tinymce.each; 824 825 tinymce.create('static tinymce.util.Cookie', { 826 getHash : function(n) { 827 var v = this.get(n), h; 828 829 if (v) { 830 each(v.split('&'), function(v) { 831 v = v.split('='); 832 h = h || {}; 833 h[unescape(v[0])] = unescape(v[1]); 834 }); 835 } 836 837 return h; 838 }, 839 840 setHash : function(n, v, e, p, d, s) { 841 var o = ''; 842 843 each(v, function(v, k) { 844 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 845 }); 846 847 this.set(n, o, e, p, d, s); 848 }, 849 850 get : function(n) { 851 var c = document.cookie, e, p = n + "=", b; 852 853 // Strict mode 854 if (!c) 855 return; 856 857 b = c.indexOf("; " + p); 858 859 if (b == -1) { 860 b = c.indexOf(p); 861 862 if (b !== 0) 863 return null; 864 } else 865 b += 2; 866 867 e = c.indexOf(";", b); 868 869 if (e == -1) 870 e = c.length; 871 872 return unescape(c.substring(b + p.length, e)); 873 }, 874 875 set : function(n, v, e, p, d, s) { 876 document.cookie = n + "=" + escape(v) + 877 ((e) ? "; expires=" + e.toGMTString() : "") + 878 ((p) ? "; path=" + escape(p) : "") + 879 ((d) ? "; domain=" + d : "") + 880 ((s) ? "; secure" : ""); 881 }, 882 883 remove : function(name, path, domain) { 884 var date = new Date(); 885 886 date.setTime(date.getTime() - 1000); 887 888 this.set(name, '', date, path, domain); 889 } 890 }); 891 })(); 892 893 (function() { 894 function serialize(o, quote) { 895 var i, v, t, name; 896 897 quote = quote || '"'; 898 899 if (o == null) 900 return 'null'; 901 902 t = typeof o; 903 904 if (t == 'string') { 905 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 906 907 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 908 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 909 if (quote === '"' && a === "'") 910 return a; 911 912 i = v.indexOf(b); 913 914 if (i + 1) 915 return '\\' + v.charAt(i + 1); 916 917 a = b.charCodeAt().toString(16); 918 919 return '\\u' + '0000'.substring(a.length) + a; 920 }) + quote; 921 } 922 923 if (t == 'object') { 924 if (o.hasOwnProperty && o instanceof Array) { 925 for (i=0, v = '['; i<o.length; i++) 926 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 927 928 return v + ']'; 929 } 930 931 v = '{'; 932 933 for (name in o) { 934 if (o.hasOwnProperty(name)) { 935 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 936 } 937 } 938 939 return v + '}'; 940 } 941 942 return '' + o; 943 }; 944 945 tinymce.util.JSON = { 946 serialize: serialize, 947 948 parse: function(s) { 949 try { 950 return eval('(' + s + ')'); 951 } catch (ex) { 952 // Ignore 953 } 954 } 955 956 }; 957 })(); 958 959 tinymce.create('static tinymce.util.XHR', { 960 send : function(o) { 961 var x, t, w = window, c = 0; 962 963 function ready() { 964 if (!o.async || x.readyState == 4 || c++ > 10000) { 965 if (o.success && c < 10000 && x.status == 200) 966 o.success.call(o.success_scope, '' + x.responseText, x, o); 967 else if (o.error) 968 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 969 970 x = null; 971 } else 972 w.setTimeout(ready, 10); 973 }; 974 975 // Default settings 976 o.scope = o.scope || this; 977 o.success_scope = o.success_scope || o.scope; 978 o.error_scope = o.error_scope || o.scope; 979 o.async = o.async === false ? false : true; 980 o.data = o.data || ''; 981 982 function get(s) { 983 x = 0; 984 985 try { 986 x = new ActiveXObject(s); 987 } catch (ex) { 988 } 989 990 return x; 991 }; 992 993 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 994 995 if (x) { 996 if (x.overrideMimeType) 997 x.overrideMimeType(o.content_type); 998 999 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1000 1001 if (o.content_type) 1002 x.setRequestHeader('Content-Type', o.content_type); 1003 1004 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1005 1006 x.send(o.data); 1007 1008 // Syncronous request 1009 if (!o.async) 1010 return ready(); 1011 1012 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1013 t = w.setTimeout(ready, 10); 1014 } 1015 } 1016 }); 1017 1018 (function() { 1019 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1020 1021 tinymce.create('tinymce.util.JSONRequest', { 1022 JSONRequest : function(s) { 1023 this.settings = extend({ 1024 }, s); 1025 this.count = 0; 1026 }, 1027 1028 send : function(o) { 1029 var ecb = o.error, scb = o.success; 1030 1031 o = extend(this.settings, o); 1032 1033 o.success = function(c, x) { 1034 c = JSON.parse(c); 1035 1036 if (typeof(c) == 'undefined') { 1037 c = { 1038 error : 'JSON Parse error.' 1039 }; 1040 } 1041 1042 if (c.error) 1043 ecb.call(o.error_scope || o.scope, c.error, x); 1044 else 1045 scb.call(o.success_scope || o.scope, c.result); 1046 }; 1047 1048 o.error = function(ty, x) { 1049 if (ecb) 1050 ecb.call(o.error_scope || o.scope, ty, x); 1051 }; 1052 1053 o.data = JSON.serialize({ 1054 id : o.id || 'c' + (this.count++), 1055 method : o.method, 1056 params : o.params 1057 }); 1058 1059 // JSON content type for Ruby on rails. Bug: #1883287 1060 o.content_type = 'application/json'; 1061 1062 XHR.send(o); 1063 }, 1064 1065 'static' : { 1066 sendRPC : function(o) { 1067 return new tinymce.util.JSONRequest().send(o); 1068 } 1069 } 1070 }); 1071 }()); 1072 (function(tinymce){ 1073 tinymce.VK = { 1074 BACKSPACE: 8, 1075 DELETE: 46, 1076 DOWN: 40, 1077 ENTER: 13, 1078 LEFT: 37, 1079 RIGHT: 39, 1080 SPACEBAR: 32, 1081 TAB: 9, 1082 UP: 38, 1083 1084 modifierPressed: function (e) { 1085 return e.shiftKey || e.ctrlKey || e.altKey; 1086 }, 1087 1088 metaKeyPressed: function(e) { 1089 return tinymce.isMac ? e.metaKey : e.ctrlKey; 1090 } 1091 }; 1092 })(tinymce); 1093 1094 tinymce.util.Quirks = function(editor) { 1095 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; 1096 1097 function setEditorCommandState(cmd, state) { 1098 try { 1099 editor.getDoc().execCommand(cmd, false, state); 1100 } catch (ex) { 1101 // Ignore 1102 } 1103 } 1104 1105 function getDocumentMode() { 1106 var documentMode = editor.getDoc().documentMode; 1107 1108 return documentMode ? documentMode : 6; 1109 }; 1110 1111 function cleanupStylesWhenDeleting() { 1112 function removeMergedFormatSpans(isDelete) { 1113 var rng, blockElm, node, clonedSpan; 1114 1115 rng = selection.getRng(); 1116 1117 // Find root block 1118 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1119 1120 // On delete clone the root span of the next block element 1121 if (isDelete) 1122 blockElm = dom.getNext(blockElm, dom.isBlock); 1123 1124 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace 1125 if (blockElm) { 1126 node = blockElm.firstChild; 1127 1128 // Ignore empty text nodes 1129 while (node && node.nodeType == 3 && node.nodeValue.length === 0) 1130 node = node.nextSibling; 1131 1132 if (node && node.nodeName === 'SPAN') { 1133 clonedSpan = node.cloneNode(false); 1134 } 1135 } 1136 1137 // Do the backspace/delete action 1138 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1139 1140 // Find all odd apple-style-spans 1141 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1142 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { 1143 var bm = selection.getBookmark(); 1144 1145 if (clonedSpan) { 1146 dom.replace(clonedSpan.cloneNode(false), span, true); 1147 } else { 1148 dom.remove(span, true); 1149 } 1150 1151 // Restore the selection 1152 selection.moveToBookmark(bm); 1153 }); 1154 }; 1155 1156 editor.onKeyDown.add(function(editor, e) { 1157 var isDelete; 1158 1159 isDelete = e.keyCode == DELETE; 1160 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1161 e.preventDefault(); 1162 removeMergedFormatSpans(isDelete); 1163 } 1164 }); 1165 1166 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1167 }; 1168 1169 function emptyEditorWhenDeleting() { 1170 function serializeRng(rng) { 1171 var body = dom.create("body"); 1172 var contents = rng.cloneContents(); 1173 body.appendChild(contents); 1174 return selection.serializer.serialize(body, {format: 'html'}); 1175 } 1176 1177 function allContentsSelected(rng) { 1178 var selection = serializeRng(rng); 1179 1180 var allRng = dom.createRng(); 1181 allRng.selectNode(editor.getBody()); 1182 1183 var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection); 1184 return selection === allSelection; 1185 } 1186 1187 editor.onKeyDown.add(function(editor, e) { 1188 var keyCode = e.keyCode, isCollapsed; 1189 1190 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1191 if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { 1192 isCollapsed = editor.selection.isCollapsed(); 1193 1194 // Selection is collapsed but the editor isn't empty 1195 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1196 return; 1197 } 1198 1199 // IE deletes all contents correctly when everything is selected 1200 if (tinymce.isIE && !isCollapsed) { 1201 return; 1202 } 1203 1204 // Selection isn't collapsed but not all the contents is selected 1205 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1206 return; 1207 } 1208 1209 // Manually empty the editor 1210 editor.setContent(''); 1211 editor.selection.setCursorLocation(editor.getBody(), 0); 1212 editor.nodeChanged(); 1213 } 1214 }); 1215 }; 1216 1217 function selectAll() { 1218 editor.onKeyDown.add(function(editor, e) { 1219 if (e.keyCode == 65 && VK.metaKeyPressed(e)) { 1220 e.preventDefault(); 1221 editor.execCommand('SelectAll'); 1222 } 1223 }); 1224 }; 1225 1226 function inputMethodFocus() { 1227 if (!editor.settings.content_editable) { 1228 // Case 1 IME doesn't initialize if you focus the document 1229 dom.bind(editor.getDoc(), 'focusin', function(e) { 1230 selection.setRng(selection.getRng()); 1231 }); 1232 1233 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1234 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1235 if (e.target == editor.getDoc().documentElement) { 1236 editor.getWin().focus(); 1237 selection.setRng(selection.getRng()); 1238 } 1239 }); 1240 } 1241 }; 1242 1243 function removeHrOnBackspace() { 1244 editor.onKeyDown.add(function(editor, e) { 1245 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1246 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1247 var node = selection.getNode(); 1248 var previousSibling = node.previousSibling; 1249 1250 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1251 dom.remove(previousSibling); 1252 tinymce.dom.Event.cancel(e); 1253 } 1254 } 1255 } 1256 }) 1257 } 1258 1259 function focusBody() { 1260 // Fix for a focus bug in FF 3.x where the body element 1261 // wouldn't get proper focus if the user clicked on the HTML element 1262 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1263 editor.onMouseDown.add(function(editor, e) { 1264 if (e.target.nodeName === "HTML") { 1265 var body = editor.getBody(); 1266 1267 // Blur the body it's focused but not correctly focused 1268 body.blur(); 1269 1270 // Refocus the body after a little while 1271 setTimeout(function() { 1272 body.focus(); 1273 }, 0); 1274 } 1275 }); 1276 } 1277 }; 1278 1279 function selectControlElements() { 1280 editor.onClick.add(function(editor, e) { 1281 e = e.target; 1282 1283 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1284 // WebKit can't even do simple things like selecting an image 1285 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1286 if (/^(IMG|HR)$/.test(e.nodeName)) { 1287 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1288 } 1289 1290 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1291 selection.select(e); 1292 } 1293 1294 editor.nodeChanged(); 1295 }); 1296 }; 1297 1298 function removeStylesWhenDeletingAccrossBlockElements() { 1299 function getAttributeApplyFunction() { 1300 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1301 1302 return function() { 1303 var target = selection.getStart(); 1304 1305 if (target !== editor.getBody()) { 1306 dom.setAttrib(target, "style", null); 1307 1308 tinymce.each(template, function(attr) { 1309 target.setAttributeNode(attr.cloneNode(true)); 1310 }); 1311 } 1312 }; 1313 } 1314 1315 function isSelectionAcrossElements() { 1316 return !selection.isCollapsed() && selection.getStart() != selection.getEnd(); 1317 } 1318 1319 function blockEvent(editor, e) { 1320 e.preventDefault(); 1321 return false; 1322 } 1323 1324 editor.onKeyPress.add(function(editor, e) { 1325 var applyAttributes; 1326 1327 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1328 applyAttributes = getAttributeApplyFunction(); 1329 editor.getDoc().execCommand('delete', false, null); 1330 applyAttributes(); 1331 e.preventDefault(); 1332 return false; 1333 } 1334 }); 1335 1336 dom.bind(editor.getDoc(), 'cut', function(e) { 1337 var applyAttributes; 1338 1339 if (isSelectionAcrossElements()) { 1340 applyAttributes = getAttributeApplyFunction(); 1341 editor.onKeyUp.addToTop(blockEvent); 1342 1343 setTimeout(function() { 1344 applyAttributes(); 1345 editor.onKeyUp.remove(blockEvent); 1346 }, 0); 1347 } 1348 }); 1349 } 1350 1351 function selectionChangeNodeChanged() { 1352 var lastRng, selectionTimer; 1353 1354 dom.bind(editor.getDoc(), 'selectionchange', function() { 1355 if (selectionTimer) { 1356 clearTimeout(selectionTimer); 1357 selectionTimer = 0; 1358 } 1359 1360 selectionTimer = window.setTimeout(function() { 1361 var rng = selection.getRng(); 1362 1363 // Compare the ranges to see if it was a real change or not 1364 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1365 editor.nodeChanged(); 1366 lastRng = rng; 1367 } 1368 }, 50); 1369 }); 1370 } 1371 1372 function ensureBodyHasRoleApplication() { 1373 document.body.setAttribute("role", "application"); 1374 } 1375 1376 function disableBackspaceIntoATable() { 1377 editor.onKeyDown.add(function(editor, e) { 1378 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1379 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1380 var previousSibling = selection.getNode().previousSibling; 1381 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1382 return tinymce.dom.Event.cancel(e); 1383 } 1384 } 1385 } 1386 }) 1387 } 1388 1389 function addNewLinesBeforeBrInPre() { 1390 // IE8+ rendering mode does the right thing with BR in PRE 1391 if (getDocumentMode() > 7) { 1392 return; 1393 } 1394 1395 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1396 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1397 setEditorCommandState('RespectVisibilityInDesign', true); 1398 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1399 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1400 1401 // Adds a \n before all BR elements in PRE to get them visual 1402 editor.parser.addNodeFilter('pre', function(nodes, name) { 1403 var i = nodes.length, brNodes, j, brElm, sibling; 1404 1405 while (i--) { 1406 brNodes = nodes[i].getAll('br'); 1407 j = brNodes.length; 1408 while (j--) { 1409 brElm = brNodes[j]; 1410 1411 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1412 sibling = brElm.prev; 1413 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1414 sibling.value += '\n'; 1415 } else { 1416 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1417 } 1418 } 1419 } 1420 }); 1421 1422 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1423 editor.serializer.addNodeFilter('pre', function(nodes, name) { 1424 var i = nodes.length, brNodes, j, brElm, sibling; 1425 1426 while (i--) { 1427 brNodes = nodes[i].getAll('br'); 1428 j = brNodes.length; 1429 while (j--) { 1430 brElm = brNodes[j]; 1431 sibling = brElm.prev; 1432 if (sibling && sibling.type == 3) { 1433 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1434 } 1435 } 1436 } 1437 }); 1438 } 1439 1440 function removePreSerializedStylesWhenSelectingControls() { 1441 dom.bind(editor.getBody(), 'mouseup', function(e) { 1442 var value, node = selection.getNode(); 1443 1444 // Moved styles to attributes on IMG eements 1445 if (node.nodeName == 'IMG') { 1446 // Convert style width to width attribute 1447 if (value = dom.getStyle(node, 'width')) { 1448 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1449 dom.setStyle(node, 'width', ''); 1450 } 1451 1452 // Convert style height to height attribute 1453 if (value = dom.getStyle(node, 'height')) { 1454 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1455 dom.setStyle(node, 'height', ''); 1456 } 1457 } 1458 }); 1459 } 1460 1461 function keepInlineElementOnDeleteBackspace() { 1462 editor.onKeyDown.add(function(editor, e) { 1463 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1464 1465 isDelete = e.keyCode == DELETE; 1466 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1467 rng = selection.getRng(); 1468 container = rng.startContainer; 1469 offset = rng.startOffset; 1470 collapsed = rng.collapsed; 1471 1472 // Override delete if the start container is a text node and is at the beginning of text or 1473 // just before/after the last character to be deleted in collapsed mode 1474 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1475 nonEmptyElements = editor.schema.getNonEmptyElements(); 1476 1477 // Prevent default logic since it's broken 1478 e.preventDefault(); 1479 1480 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1481 brElm = dom.create('br', {id: '__tmp'}); 1482 container.parentNode.insertBefore(brElm, container); 1483 1484 // Do the browser delete 1485 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1486 1487 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1488 container = selection.getRng().startContainer; 1489 sibling = container.previousSibling; 1490 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1491 dom.remove(sibling); 1492 } 1493 1494 // Remove the temp element we inserted 1495 dom.remove('__tmp'); 1496 } 1497 } 1498 }); 1499 } 1500 1501 function removeBlockQuoteOnBackSpace() { 1502 // Add block quote deletion handler 1503 editor.onKeyDown.add(function(editor, e) { 1504 var rng, container, offset, root, parent; 1505 1506 if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { 1507 return; 1508 } 1509 1510 rng = selection.getRng(); 1511 container = rng.startContainer; 1512 offset = rng.startOffset; 1513 root = dom.getRoot(); 1514 parent = container; 1515 1516 if (!rng.collapsed || offset !== 0) { 1517 return; 1518 } 1519 1520 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1521 parent = parent.parentNode; 1522 } 1523 1524 // Is the cursor at the beginning of a blockquote? 1525 if (parent.tagName === 'BLOCKQUOTE') { 1526 // Remove the blockquote 1527 editor.formatter.toggle('blockquote', null, parent); 1528 1529 // Move the caret to the beginning of container 1530 rng.setStart(container, 0); 1531 rng.setEnd(container, 0); 1532 selection.setRng(rng); 1533 selection.collapse(false); 1534 } 1535 }); 1536 }; 1537 1538 function setGeckoEditingOptions() { 1539 function setOpts() { 1540 editor._refreshContentEditable(); 1541 1542 setEditorCommandState("StyleWithCSS", false); 1543 setEditorCommandState("enableInlineTableEditing", false); 1544 1545 if (!settings.object_resizing) { 1546 setEditorCommandState("enableObjectResizing", false); 1547 } 1548 }; 1549 1550 if (!settings.readonly) { 1551 editor.onBeforeExecCommand.add(setOpts); 1552 editor.onMouseDown.add(setOpts); 1553 } 1554 }; 1555 1556 function addBrAfterLastLinks() { 1557 function fixLinks(editor, o) { 1558 tinymce.each(dom.select('a'), function(node) { 1559 var parentNode = node.parentNode, root = dom.getRoot(); 1560 1561 if (parentNode.lastChild === node) { 1562 while (parentNode && !dom.isBlock(parentNode)) { 1563 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1564 return; 1565 } 1566 1567 parentNode = parentNode.parentNode; 1568 } 1569 1570 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1571 } 1572 }); 1573 }; 1574 1575 editor.onExecCommand.add(function(editor, cmd) { 1576 if (cmd === 'CreateLink') { 1577 fixLinks(editor); 1578 } 1579 }); 1580 1581 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1582 }; 1583 1584 function setDefaultBlockType() { 1585 if (settings.forced_root_block) { 1586 editor.onInit.add(function() { 1587 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1588 }); 1589 } 1590 } 1591 1592 function removeGhostSelection() { 1593 function repaint(sender, args) { 1594 if (!sender || !args.initial) { 1595 editor.execCommand('mceRepaint'); 1596 } 1597 }; 1598 1599 editor.onUndo.add(repaint); 1600 editor.onRedo.add(repaint); 1601 editor.onSetContent.add(repaint); 1602 }; 1603 1604 function deleteControlItemOnBackSpace() { 1605 editor.onKeyDown.add(function(editor, e) { 1606 var rng; 1607 1608 if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { 1609 rng = editor.getDoc().selection.createRange(); 1610 if (rng && rng.item) { 1611 e.preventDefault(); 1612 editor.undoManager.beforeChange(); 1613 dom.remove(rng.item(0)); 1614 editor.undoManager.add(); 1615 } 1616 } 1617 }); 1618 }; 1619 1620 function renderEmptyBlocksFix() { 1621 var emptyBlocksCSS; 1622 1623 // IE10+ 1624 if (getDocumentMode() >= 10) { 1625 emptyBlocksCSS = ''; 1626 tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1627 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1628 }); 1629 1630 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1631 } 1632 }; 1633 1634 function fakeImageResize() { 1635 var mouseDownImg, startX, startY, startW, startH; 1636 1637 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1638 return; 1639 } 1640 1641 editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); 1642 1643 function resizeImage(e) { 1644 var deltaX, deltaY, ratio, width, height; 1645 1646 if (mouseDownImg) { 1647 deltaX = e.screenX - startX; 1648 deltaY = e.screenY - startY; 1649 ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); 1650 1651 // Only update styles if the user draged one pixel or more 1652 if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { 1653 // Constrain proportions 1654 width = Math.round(startW * ratio); 1655 height = Math.round(startH * ratio); 1656 1657 // Resize by using style or attribute 1658 if (mouseDownImg.style.width) { 1659 dom.setStyle(mouseDownImg, 'width', width); 1660 } else { 1661 dom.setAttrib(mouseDownImg, 'width', width); 1662 } 1663 1664 // Resize by using style or attribute 1665 if (mouseDownImg.style.height) { 1666 dom.setStyle(mouseDownImg, 'height', height); 1667 } else { 1668 dom.setAttrib(mouseDownImg, 'height', height); 1669 } 1670 1671 if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { 1672 dom.addClass(editor.getBody(), 'mceResizeImages'); 1673 } 1674 } 1675 } 1676 }; 1677 1678 editor.onMouseDown.add(function(editor, e) { 1679 var target = e.target; 1680 1681 if (target.nodeName == "IMG") { 1682 mouseDownImg = target; 1683 startX = e.screenX; 1684 startY = e.screenY; 1685 startW = mouseDownImg.clientWidth; 1686 startH = mouseDownImg.clientHeight; 1687 dom.bind(editor.getDoc(), 'mousemove', resizeImage); 1688 e.preventDefault(); 1689 } 1690 }); 1691 1692 // Unbind events on node change and restore resize cursor 1693 editor.onNodeChange.add(function() { 1694 if (mouseDownImg) { 1695 mouseDownImg = null; 1696 dom.unbind(editor.getDoc(), 'mousemove', resizeImage); 1697 } 1698 1699 if (selection.getNode().nodeName == "IMG") { 1700 dom.addClass(editor.getBody(), 'mceResizeImages'); 1701 } else { 1702 dom.removeClass(editor.getBody(), 'mceResizeImages'); 1703 } 1704 }); 1705 }; 1706 1707 // All browsers 1708 disableBackspaceIntoATable(); 1709 removeBlockQuoteOnBackSpace(); 1710 emptyEditorWhenDeleting(); 1711 1712 // WebKit 1713 if (tinymce.isWebKit) { 1714 keepInlineElementOnDeleteBackspace(); 1715 cleanupStylesWhenDeleting(); 1716 inputMethodFocus(); 1717 selectControlElements(); 1718 setDefaultBlockType(); 1719 1720 // iOS 1721 if (tinymce.isIDevice) { 1722 selectionChangeNodeChanged(); 1723 } else { 1724 fakeImageResize(); 1725 selectAll(); 1726 } 1727 } 1728 1729 // IE 1730 if (tinymce.isIE) { 1731 removeHrOnBackspace(); 1732 ensureBodyHasRoleApplication(); 1733 addNewLinesBeforeBrInPre(); 1734 removePreSerializedStylesWhenSelectingControls(); 1735 deleteControlItemOnBackSpace(); 1736 renderEmptyBlocksFix(); 1737 } 1738 1739 // Gecko 1740 if (tinymce.isGecko) { 1741 removeHrOnBackspace(); 1742 focusBody(); 1743 removeStylesWhenDeletingAccrossBlockElements(); 1744 setGeckoEditingOptions(); 1745 addBrAfterLastLinks(); 1746 removeGhostSelection(); 1747 } 1748 }; 1749 (function(tinymce) { 1750 var namedEntities, baseEntities, reverseEntities, 1751 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1752 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1753 rawCharsRegExp = /[<>&\"\']/g, 1754 entityRegExp = /&(#x|#)?([\w]+);/g, 1755 asciiMap = { 1756 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 1757 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 1758 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 1759 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 1760 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 1761 }; 1762 1763 // Raw entities 1764 baseEntities = { 1765 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 1766 "'" : ''', 1767 '<' : '<', 1768 '>' : '>', 1769 '&' : '&' 1770 }; 1771 1772 // Reverse lookup table for raw entities 1773 reverseEntities = { 1774 '<' : '<', 1775 '>' : '>', 1776 '&' : '&', 1777 '"' : '"', 1778 ''' : "'" 1779 }; 1780 1781 // Decodes text by using the browser 1782 function nativeDecode(text) { 1783 var elm; 1784 1785 elm = document.createElement("div"); 1786 elm.innerHTML = text; 1787 1788 return elm.textContent || elm.innerText || text; 1789 }; 1790 1791 // Build a two way lookup table for the entities 1792 function buildEntitiesLookup(items, radix) { 1793 var i, chr, entity, lookup = {}; 1794 1795 if (items) { 1796 items = items.split(','); 1797 radix = radix || 10; 1798 1799 // Build entities lookup table 1800 for (i = 0; i < items.length; i += 2) { 1801 chr = String.fromCharCode(parseInt(items[i], radix)); 1802 1803 // Only add non base entities 1804 if (!baseEntities[chr]) { 1805 entity = '&' + items[i + 1] + ';'; 1806 lookup[chr] = entity; 1807 lookup[entity] = chr; 1808 } 1809 } 1810 1811 return lookup; 1812 } 1813 }; 1814 1815 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 1816 namedEntities = buildEntitiesLookup( 1817 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 1818 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 1819 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 1820 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 1821 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 1822 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 1823 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 1824 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 1825 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 1826 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 1827 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 1828 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 1829 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 1830 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 1831 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 1832 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 1833 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 1834 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 1835 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 1836 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 1837 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 1838 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 1839 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 1840 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 1841 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 1842 1843 tinymce.html = tinymce.html || {}; 1844 1845 tinymce.html.Entities = { 1846 encodeRaw : function(text, attr) { 1847 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1848 return baseEntities[chr] || chr; 1849 }); 1850 }, 1851 1852 encodeAllRaw : function(text) { 1853 return ('' + text).replace(rawCharsRegExp, function(chr) { 1854 return baseEntities[chr] || chr; 1855 }); 1856 }, 1857 1858 encodeNumeric : function(text, attr) { 1859 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1860 // Multi byte sequence convert it to a single entity 1861 if (chr.length > 1) 1862 return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 1863 1864 return baseEntities[chr] || '' + chr.charCodeAt(0) + ';'; 1865 }); 1866 }, 1867 1868 encodeNamed : function(text, attr, entities) { 1869 entities = entities || namedEntities; 1870 1871 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1872 return baseEntities[chr] || entities[chr] || chr; 1873 }); 1874 }, 1875 1876 getEncodeFunc : function(name, entities) { 1877 var Entities = tinymce.html.Entities; 1878 1879 entities = buildEntitiesLookup(entities) || namedEntities; 1880 1881 function encodeNamedAndNumeric(text, attr) { 1882 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1883 return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr; 1884 }); 1885 }; 1886 1887 function encodeCustomNamed(text, attr) { 1888 return Entities.encodeNamed(text, attr, entities); 1889 }; 1890 1891 // Replace + with , to be compatible with previous TinyMCE versions 1892 name = tinymce.makeMap(name.replace(/\+/g, ',')); 1893 1894 // Named and numeric encoder 1895 if (name.named && name.numeric) 1896 return encodeNamedAndNumeric; 1897 1898 // Named encoder 1899 if (name.named) { 1900 // Custom names 1901 if (entities) 1902 return encodeCustomNamed; 1903 1904 return Entities.encodeNamed; 1905 } 1906 1907 // Numeric 1908 if (name.numeric) 1909 return Entities.encodeNumeric; 1910 1911 // Raw encoder 1912 return Entities.encodeRaw; 1913 }, 1914 1915 decode : function(text) { 1916 return text.replace(entityRegExp, function(all, numeric, value) { 1917 if (numeric) { 1918 value = parseInt(value, numeric.length === 2 ? 16 : 10); 1919 1920 // Support upper UTF 1921 if (value > 0xFFFF) { 1922 value -= 0x10000; 1923 1924 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 1925 } else 1926 return asciiMap[value] || String.fromCharCode(value); 1927 } 1928 1929 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 1930 }); 1931 } 1932 }; 1933 })(tinymce); 1934 1935 tinymce.html.Styles = function(settings, schema) { 1936 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 1937 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 1938 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 1939 trimRightRegExp = /\s+$/, 1940 urlColorRegExp = /rgb/, 1941 undef, i, encodingLookup = {}, encodingItems; 1942 1943 settings = settings || {}; 1944 1945 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 1946 for (i = 0; i < encodingItems.length; i++) { 1947 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 1948 encodingLookup['\uFEFF' + i] = encodingItems[i]; 1949 } 1950 1951 function toHex(match, r, g, b) { 1952 function hex(val) { 1953 val = parseInt(val).toString(16); 1954 1955 return val.length > 1 ? val : '0' + val; // 0 -> 00 1956 }; 1957 1958 return '#' + hex(r) + hex(g) + hex(b); 1959 }; 1960 1961 return { 1962 toHex : function(color) { 1963 return color.replace(rgbRegExp, toHex); 1964 }, 1965 1966 parse : function(css) { 1967 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 1968 1969 function compress(prefix, suffix) { 1970 var top, right, bottom, left; 1971 1972 // Get values and check it it needs compressing 1973 top = styles[prefix + '-top' + suffix]; 1974 if (!top) 1975 return; 1976 1977 right = styles[prefix + '-right' + suffix]; 1978 if (top != right) 1979 return; 1980 1981 bottom = styles[prefix + '-bottom' + suffix]; 1982 if (right != bottom) 1983 return; 1984 1985 left = styles[prefix + '-left' + suffix]; 1986 if (bottom != left) 1987 return; 1988 1989 // Compress 1990 styles[prefix + suffix] = left; 1991 delete styles[prefix + '-top' + suffix]; 1992 delete styles[prefix + '-right' + suffix]; 1993 delete styles[prefix + '-bottom' + suffix]; 1994 delete styles[prefix + '-left' + suffix]; 1995 }; 1996 1997 function canCompress(key) { 1998 var value = styles[key], i; 1999 2000 if (!value || value.indexOf(' ') < 0) 2001 return; 2002 2003 value = value.split(' '); 2004 i = value.length; 2005 while (i--) { 2006 if (value[i] !== value[0]) 2007 return false; 2008 } 2009 2010 styles[key] = value[0]; 2011 2012 return true; 2013 }; 2014 2015 function compress2(target, a, b, c) { 2016 if (!canCompress(a)) 2017 return; 2018 2019 if (!canCompress(b)) 2020 return; 2021 2022 if (!canCompress(c)) 2023 return; 2024 2025 // Compress 2026 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2027 delete styles[a]; 2028 delete styles[b]; 2029 delete styles[c]; 2030 }; 2031 2032 // Encodes the specified string by replacing all \" \' ; : with _<num> 2033 function encode(str) { 2034 isEncoded = true; 2035 2036 return encodingLookup[str]; 2037 }; 2038 2039 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2040 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2041 function decode(str, keep_slashes) { 2042 if (isEncoded) { 2043 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2044 return encodingLookup[str]; 2045 }); 2046 } 2047 2048 if (!keep_slashes) 2049 str = str.replace(/\\([\'\";:])/g, "$1"); 2050 2051 return str; 2052 }; 2053 2054 function processUrl(match, url, url2, url3, str, str2) { 2055 str = str || str2; 2056 2057 if (str) { 2058 str = decode(str); 2059 2060 // Force strings into single quote format 2061 return "'" + str.replace(/\'/g, "\\'") + "'"; 2062 } 2063 2064 url = decode(url || url2 || url3); 2065 2066 // Convert the URL to relative/absolute depending on config 2067 if (urlConverter) 2068 url = urlConverter.call(urlConverterScope, url, 'style'); 2069 2070 // Output new URL format 2071 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2072 }; 2073 2074 if (css) { 2075 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2076 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2077 return str.replace(/[;:]/g, encode); 2078 }); 2079 2080 // Parse styles 2081 while (matches = styleRegExp.exec(css)) { 2082 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2083 value = matches[2].replace(trimRightRegExp, ''); 2084 2085 if (name && value.length > 0) { 2086 // Opera will produce 700 instead of bold in their style values 2087 if (name === 'font-weight' && value === '700') 2088 value = 'bold'; 2089 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2090 value = value.toLowerCase(); 2091 2092 // Convert RGB colors to HEX 2093 value = value.replace(rgbRegExp, toHex); 2094 2095 // Convert URLs and force them into url('value') format 2096 value = value.replace(urlOrStrRegExp, processUrl); 2097 styles[name] = isEncoded ? decode(value, true) : value; 2098 } 2099 2100 styleRegExp.lastIndex = matches.index + matches[0].length; 2101 } 2102 2103 // Compress the styles to reduce it's size for example IE will expand styles 2104 compress("border", ""); 2105 compress("border", "-width"); 2106 compress("border", "-color"); 2107 compress("border", "-style"); 2108 compress("padding", ""); 2109 compress("margin", ""); 2110 compress2('border', 'border-width', 'border-style', 'border-color'); 2111 2112 // Remove pointless border, IE produces these 2113 if (styles.border === 'medium none') 2114 delete styles.border; 2115 } 2116 2117 return styles; 2118 }, 2119 2120 serialize : function(styles, element_name) { 2121 var css = '', name, value; 2122 2123 function serializeStyles(name) { 2124 var styleList, i, l, value; 2125 2126 styleList = schema.styles[name]; 2127 if (styleList) { 2128 for (i = 0, l = styleList.length; i < l; i++) { 2129 name = styleList[i]; 2130 value = styles[name]; 2131 2132 if (value !== undef && value.length > 0) 2133 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2134 } 2135 } 2136 }; 2137 2138 // Serialize styles according to schema 2139 if (element_name && schema && schema.styles) { 2140 // Serialize global styles and element specific styles 2141 serializeStyles('*'); 2142 serializeStyles(element_name); 2143 } else { 2144 // Output the styles in the order they are inside the object 2145 for (name in styles) { 2146 value = styles[name]; 2147 2148 if (value !== undef && value.length > 0) 2149 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2150 } 2151 } 2152 2153 return css; 2154 } 2155 }; 2156 }; 2157 2158 (function(tinymce) { 2159 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2160 2161 function split(str, delim) { 2162 return str.split(delim || ','); 2163 }; 2164 2165 function unpack(lookup, data) { 2166 var key, elements = {}; 2167 2168 function replace(value) { 2169 return value.replace(/[A-Z]+/g, function(key) { 2170 return replace(lookup[key]); 2171 }); 2172 }; 2173 2174 // Unpack lookup 2175 for (key in lookup) { 2176 if (lookup.hasOwnProperty(key)) 2177 lookup[key] = replace(lookup[key]); 2178 } 2179 2180 // Unpack and parse data into object map 2181 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2182 attributes = split(attributes, '|'); 2183 2184 elements[name] = { 2185 attributes : makeMap(attributes), 2186 attributesOrder : attributes, 2187 children : makeMap(children, '|', {'#comment' : {}}) 2188 } 2189 }); 2190 2191 return elements; 2192 }; 2193 2194 function getHTML5() { 2195 var html5 = mapCache.html5; 2196 2197 if (!html5) { 2198 html5 = mapCache.html5 = unpack({ 2199 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2200 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2201 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2202 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2203 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2204 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2205 }, 'html[A|manifest][body|head]' + 2206 'head[A][base|command|link|meta|noscript|script|style|title]' + 2207 'title[A][#]' + 2208 'base[A|href|target][]' + 2209 'link[A|href|rel|media|type|sizes][]' + 2210 'meta[A|http-equiv|name|content|charset][]' + 2211 'style[A|type|media|scoped][#]' + 2212 'script[A|charset|type|src|defer|async][#]' + 2213 'noscript[A][C]' + 2214 'body[A][C]' + 2215 'section[A][C]' + 2216 'nav[A][C]' + 2217 'article[A][C]' + 2218 'aside[A][C]' + 2219 'h1[A][B]' + 2220 'h2[A][B]' + 2221 'h3[A][B]' + 2222 'h4[A][B]' + 2223 'h5[A][B]' + 2224 'h6[A][B]' + 2225 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2226 'header[A][C]' + 2227 'footer[A][C]' + 2228 'address[A][C]' + 2229 'p[A][B]' + 2230 'br[A][]' + 2231 'pre[A][B]' + 2232 'dialog[A][dd|dt]' + 2233 'blockquote[A|cite][C]' + 2234 'ol[A|start|reversed][li]' + 2235 'ul[A][li]' + 2236 'li[A|value][C]' + 2237 'dl[A][dd|dt]' + 2238 'dt[A][B]' + 2239 'dd[A][C]' + 2240 'a[A|href|target|ping|rel|media|type][B]' + 2241 'em[A][B]' + 2242 'strong[A][B]' + 2243 'small[A][B]' + 2244 'cite[A][B]' + 2245 'q[A|cite][B]' + 2246 'dfn[A][B]' + 2247 'abbr[A][B]' + 2248 'code[A][B]' + 2249 'var[A][B]' + 2250 'samp[A][B]' + 2251 'kbd[A][B]' + 2252 'sub[A][B]' + 2253 'sup[A][B]' + 2254 'i[A][B]' + 2255 'b[A][B]' + 2256 'mark[A][B]' + 2257 'progress[A|value|max][B]' + 2258 'meter[A|value|min|max|low|high|optimum][B]' + 2259 'time[A|datetime][B]' + 2260 'ruby[A][B|rt|rp]' + 2261 'rt[A][B]' + 2262 'rp[A][B]' + 2263 'bdo[A][B]' + 2264 'span[A][B]' + 2265 'ins[A|cite|datetime][B]' + 2266 'del[A|cite|datetime][B]' + 2267 'figure[A][C|legend|figcaption]' + 2268 'figcaption[A][C]' + 2269 'img[A|alt|src|height|width|usemap|ismap][]' + 2270 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2271 'embed[A|src|height|width|type][]' + 2272 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2273 'param[A|name|value][]' + 2274 'details[A|open][C|legend]' + 2275 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2276 'menu[A|type|label][C|li]' + 2277 'legend[A][C|B]' + 2278 'div[A][C]' + 2279 'source[A|src|type|media][]' + 2280 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2281 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2282 'hr[A][]' + 2283 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2284 'fieldset[A|disabled|form|name][C|legend]' + 2285 'label[A|form|for][B]' + 2286 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2287 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2288 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2289 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2290 'datalist[A][B|option]' + 2291 'optgroup[A|disabled|label][option]' + 2292 'option[A|disabled|selected|label|value][]' + 2293 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2294 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2295 'output[A|for|form|name][B]' + 2296 'canvas[A|width|height][]' + 2297 'map[A|name][B|C]' + 2298 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2299 'mathml[A][]' + 2300 'svg[A][]' + 2301 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2302 'caption[A][C]' + 2303 'colgroup[A|span][col]' + 2304 'col[A|span][]' + 2305 'thead[A][tr]' + 2306 'tfoot[A][tr]' + 2307 'tbody[A][tr]' + 2308 'tr[A][th|td]' + 2309 'th[A|headers|rowspan|colspan|scope][B]' + 2310 'td[A|headers|rowspan|colspan][C]' + 2311 'wbr[A][]' 2312 ); 2313 } 2314 2315 return html5; 2316 }; 2317 2318 function getHTML4() { 2319 var html4 = mapCache.html4; 2320 2321 if (!html4) { 2322 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2323 html4 = mapCache.html4 = unpack({ 2324 Z : 'H|K|N|O|P', 2325 Y : 'X|form|R|Q', 2326 ZG : 'E|span|width|align|char|charoff|valign', 2327 X : 'p|T|div|U|W|isindex|fieldset|table', 2328 ZF : 'E|align|char|charoff|valign', 2329 W : 'pre|hr|blockquote|address|center|noframes', 2330 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2331 ZD : '[E][S]', 2332 U : 'ul|ol|dl|menu|dir', 2333 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2334 T : 'h1|h2|h3|h4|h5|h6', 2335 ZB : 'X|S|Q', 2336 S : 'R|P', 2337 ZA : 'a|G|J|M|O|P', 2338 R : 'a|H|K|N|O', 2339 Q : 'noscript|P', 2340 P : 'ins|del|script', 2341 O : 'input|select|textarea|label|button', 2342 N : 'M|L', 2343 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2344 L : 'sub|sup', 2345 K : 'J|I', 2346 J : 'tt|i|b|u|s|strike', 2347 I : 'big|small|font|basefont', 2348 H : 'G|F', 2349 G : 'br|span|bdo', 2350 F : 'object|applet|img|map|iframe', 2351 E : 'A|B|C', 2352 D : 'accesskey|tabindex|onfocus|onblur', 2353 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2354 B : 'lang|xml:lang|dir', 2355 A : 'id|class|style|title' 2356 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2357 'style[B|id|type|media|title|xml:space][]' + 2358 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2359 'param[id|name|value|valuetype|type][]' + 2360 'p[E|align][#|S]' + 2361 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2362 'br[A|clear][]' + 2363 'span[E][#|S]' + 2364 'bdo[A|C|B][#|S]' + 2365 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2366 'h1[E|align][#|S]' + 2367 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2368 'map[B|C|A|name][X|form|Q|area]' + 2369 'h2[E|align][#|S]' + 2370 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2371 'h3[E|align][#|S]' + 2372 'tt[E][#|S]' + 2373 'i[E][#|S]' + 2374 'b[E][#|S]' + 2375 'u[E][#|S]' + 2376 's[E][#|S]' + 2377 'strike[E][#|S]' + 2378 'big[E][#|S]' + 2379 'small[E][#|S]' + 2380 'font[A|B|size|color|face][#|S]' + 2381 'basefont[id|size|color|face][]' + 2382 'em[E][#|S]' + 2383 'strong[E][#|S]' + 2384 'dfn[E][#|S]' + 2385 'code[E][#|S]' + 2386 'q[E|cite][#|S]' + 2387 'samp[E][#|S]' + 2388 'kbd[E][#|S]' + 2389 'var[E][#|S]' + 2390 'cite[E][#|S]' + 2391 'abbr[E][#|S]' + 2392 'acronym[E][#|S]' + 2393 'sub[E][#|S]' + 2394 'sup[E][#|S]' + 2395 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2396 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2397 'optgroup[E|disabled|label][option]' + 2398 'option[E|selected|disabled|label|value][]' + 2399 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2400 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2401 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2402 'h4[E|align][#|S]' + 2403 'ins[E|cite|datetime][#|Y]' + 2404 'h5[E|align][#|S]' + 2405 'del[E|cite|datetime][#|Y]' + 2406 'h6[E|align][#|S]' + 2407 'div[E|align][#|Y]' + 2408 'ul[E|type|compact][li]' + 2409 'li[E|type|value][#|Y]' + 2410 'ol[E|type|compact|start][li]' + 2411 'dl[E|compact][dt|dd]' + 2412 'dt[E][#|S]' + 2413 'dd[E][#|Y]' + 2414 'menu[E|compact][li]' + 2415 'dir[E|compact][li]' + 2416 'pre[E|width|xml:space][#|ZA]' + 2417 'hr[E|align|noshade|size|width][]' + 2418 'blockquote[E|cite][#|Y]' + 2419 'address[E][#|S|p]' + 2420 'center[E][#|Y]' + 2421 'noframes[E][#|Y]' + 2422 'isindex[A|B|prompt][]' + 2423 'fieldset[E][#|legend|Y]' + 2424 'legend[E|accesskey|align][#|S]' + 2425 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2426 'caption[E|align][#|S]' + 2427 'col[ZG][]' + 2428 'colgroup[ZG][col]' + 2429 'thead[ZF][tr]' + 2430 'tr[ZF|bgcolor][th|td]' + 2431 'th[E|ZE][#|Y]' + 2432 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2433 'noscript[E][#|Y]' + 2434 'td[E|ZE][#|Y]' + 2435 'tfoot[ZF][tr]' + 2436 'tbody[ZF][tr]' + 2437 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2438 'base[id|href|target][]' + 2439 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2440 ); 2441 } 2442 2443 return html4; 2444 }; 2445 2446 tinymce.html.Schema = function(settings) { 2447 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2448 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2449 2450 // Creates an lookup table map object for the specified option or the default value 2451 function createLookupTable(option, default_value, extend) { 2452 var value = settings[option]; 2453 2454 if (!value) { 2455 // Get cached default map or make it if needed 2456 value = mapCache[option]; 2457 2458 if (!value) { 2459 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2460 value = tinymce.extend(value, extend); 2461 2462 mapCache[option] = value; 2463 } 2464 } else { 2465 // Create custom map 2466 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2467 } 2468 2469 return value; 2470 }; 2471 2472 settings = settings || {}; 2473 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2474 2475 // Allow all elements and attributes if verify_html is set to false 2476 if (settings.verify_html === false) 2477 settings.valid_elements = '*[*]'; 2478 2479 // Build styles list 2480 if (settings.valid_styles) { 2481 validStyles = {}; 2482 2483 // Convert styles into a rule list 2484 each(settings.valid_styles, function(value, key) { 2485 validStyles[key] = tinymce.explode(value); 2486 }); 2487 } 2488 2489 // Setup map objects 2490 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); 2491 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2492 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2493 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2494 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); 2495 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 2496 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 2497 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); 2498 2499 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2500 function patternToRegExp(str) { 2501 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2502 }; 2503 2504 // Parses the specified valid_elements string and adds to the current rules 2505 // This function is a bit hard to read since it's heavily optimized for speed 2506 function addValidElements(valid_elements) { 2507 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2508 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2509 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2510 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2511 hasPatternsRegExp = /[*?+]/; 2512 2513 if (valid_elements) { 2514 // Split valid elements into an array with rules 2515 valid_elements = split(valid_elements); 2516 2517 if (elements['@']) { 2518 globalAttributes = elements['@'].attributes; 2519 globalAttributesOrder = elements['@'].attributesOrder; 2520 } 2521 2522 // Loop all rules 2523 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2524 // Parse element rule 2525 matches = elementRuleRegExp.exec(valid_elements[ei]); 2526 if (matches) { 2527 // Setup local names for matches 2528 prefix = matches[1]; 2529 elementName = matches[2]; 2530 outputName = matches[3]; 2531 attrData = matches[4]; 2532 2533 // Create new attributes and attributesOrder 2534 attributes = {}; 2535 attributesOrder = []; 2536 2537 // Create the new element 2538 element = { 2539 attributes : attributes, 2540 attributesOrder : attributesOrder 2541 }; 2542 2543 // Padd empty elements prefix 2544 if (prefix === '#') 2545 element.paddEmpty = true; 2546 2547 // Remove empty elements prefix 2548 if (prefix === '-') 2549 element.removeEmpty = true; 2550 2551 // Copy attributes from global rule into current rule 2552 if (globalAttributes) { 2553 for (key in globalAttributes) 2554 attributes[key] = globalAttributes[key]; 2555 2556 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2557 } 2558 2559 // Attributes defined 2560 if (attrData) { 2561 attrData = split(attrData, '|'); 2562 for (ai = 0, al = attrData.length; ai < al; ai++) { 2563 matches = attrRuleRegExp.exec(attrData[ai]); 2564 if (matches) { 2565 attr = {}; 2566 attrType = matches[1]; 2567 attrName = matches[2].replace(/::/g, ':'); 2568 prefix = matches[3]; 2569 value = matches[4]; 2570 2571 // Required 2572 if (attrType === '!') { 2573 element.attributesRequired = element.attributesRequired || []; 2574 element.attributesRequired.push(attrName); 2575 attr.required = true; 2576 } 2577 2578 // Denied from global 2579 if (attrType === '-') { 2580 delete attributes[attrName]; 2581 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2582 continue; 2583 } 2584 2585 // Default value 2586 if (prefix) { 2587 // Default value 2588 if (prefix === '=') { 2589 element.attributesDefault = element.attributesDefault || []; 2590 element.attributesDefault.push({name: attrName, value: value}); 2591 attr.defaultValue = value; 2592 } 2593 2594 // Forced value 2595 if (prefix === ':') { 2596 element.attributesForced = element.attributesForced || []; 2597 element.attributesForced.push({name: attrName, value: value}); 2598 attr.forcedValue = value; 2599 } 2600 2601 // Required values 2602 if (prefix === '<') 2603 attr.validValues = makeMap(value, '?'); 2604 } 2605 2606 // Check for attribute patterns 2607 if (hasPatternsRegExp.test(attrName)) { 2608 element.attributePatterns = element.attributePatterns || []; 2609 attr.pattern = patternToRegExp(attrName); 2610 element.attributePatterns.push(attr); 2611 } else { 2612 // Add attribute to order list if it doesn't already exist 2613 if (!attributes[attrName]) 2614 attributesOrder.push(attrName); 2615 2616 attributes[attrName] = attr; 2617 } 2618 } 2619 } 2620 } 2621 2622 // Global rule, store away these for later usage 2623 if (!globalAttributes && elementName == '@') { 2624 globalAttributes = attributes; 2625 globalAttributesOrder = attributesOrder; 2626 } 2627 2628 // Handle substitute elements such as b/strong 2629 if (outputName) { 2630 element.outputName = elementName; 2631 elements[outputName] = element; 2632 } 2633 2634 // Add pattern or exact element 2635 if (hasPatternsRegExp.test(elementName)) { 2636 element.pattern = patternToRegExp(elementName); 2637 patternElements.push(element); 2638 } else 2639 elements[elementName] = element; 2640 } 2641 } 2642 } 2643 }; 2644 2645 function setValidElements(valid_elements) { 2646 elements = {}; 2647 patternElements = []; 2648 2649 addValidElements(valid_elements); 2650 2651 each(schemaItems, function(element, name) { 2652 children[name] = element.children; 2653 }); 2654 }; 2655 2656 // Adds custom non HTML elements to the schema 2657 function addCustomElements(custom_elements) { 2658 var customElementRegExp = /^(~)?(.+)$/; 2659 2660 if (custom_elements) { 2661 each(split(custom_elements), function(rule) { 2662 var matches = customElementRegExp.exec(rule), 2663 inline = matches[1] === '~', 2664 cloneName = inline ? 'span' : 'div', 2665 name = matches[2]; 2666 2667 children[name] = children[cloneName]; 2668 customElementsMap[name] = cloneName; 2669 2670 // If it's not marked as inline then add it to valid block elements 2671 if (!inline) 2672 blockElementsMap[name] = {}; 2673 2674 // Add custom elements at span/div positions 2675 each(children, function(element, child) { 2676 if (element[cloneName]) 2677 element[name] = element[cloneName]; 2678 }); 2679 }); 2680 } 2681 }; 2682 2683 // Adds valid children to the schema object 2684 function addValidChildren(valid_children) { 2685 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2686 2687 if (valid_children) { 2688 each(split(valid_children), function(rule) { 2689 var matches = childRuleRegExp.exec(rule), parent, prefix; 2690 2691 if (matches) { 2692 prefix = matches[1]; 2693 2694 // Add/remove items from default 2695 if (prefix) 2696 parent = children[matches[2]]; 2697 else 2698 parent = children[matches[2]] = {'#comment' : {}}; 2699 2700 parent = children[matches[2]]; 2701 2702 each(split(matches[3], '|'), function(child) { 2703 if (prefix === '-') 2704 delete parent[child]; 2705 else 2706 parent[child] = {}; 2707 }); 2708 } 2709 }); 2710 } 2711 }; 2712 2713 function getElementRule(name) { 2714 var element = elements[name], i; 2715 2716 // Exact match found 2717 if (element) 2718 return element; 2719 2720 // No exact match then try the patterns 2721 i = patternElements.length; 2722 while (i--) { 2723 element = patternElements[i]; 2724 2725 if (element.pattern.test(name)) 2726 return element; 2727 } 2728 }; 2729 2730 if (!settings.valid_elements) { 2731 // No valid elements defined then clone the elements from the schema spec 2732 each(schemaItems, function(element, name) { 2733 elements[name] = { 2734 attributes : element.attributes, 2735 attributesOrder : element.attributesOrder 2736 }; 2737 2738 children[name] = element.children; 2739 }); 2740 2741 // Switch these on HTML4 2742 if (settings.schema != "html5") { 2743 each(split('strong/b,em/i'), function(item) { 2744 item = split(item, '/'); 2745 elements[item[1]].outputName = item[0]; 2746 }); 2747 } 2748 2749 // Add default alt attribute for images 2750 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 2751 2752 // Remove these if they are empty by default 2753 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 2754 if (elements[name]) { 2755 elements[name].removeEmpty = true; 2756 } 2757 }); 2758 2759 // Padd these by default 2760 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 2761 elements[name].paddEmpty = true; 2762 }); 2763 } else 2764 setValidElements(settings.valid_elements); 2765 2766 addCustomElements(settings.custom_elements); 2767 addValidChildren(settings.valid_children); 2768 addValidElements(settings.extended_valid_elements); 2769 2770 // Todo: Remove this when we fix list handling to be valid 2771 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 2772 2773 // Delete invalid elements 2774 if (settings.invalid_elements) { 2775 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 2776 if (elements[item]) 2777 delete elements[item]; 2778 }); 2779 } 2780 2781 // If the user didn't allow span only allow internal spans 2782 if (!getElementRule('span')) 2783 addValidElements('span[!data-mce-type|*]'); 2784 2785 self.children = children; 2786 2787 self.styles = validStyles; 2788 2789 self.getBoolAttrs = function() { 2790 return boolAttrMap; 2791 }; 2792 2793 self.getBlockElements = function() { 2794 return blockElementsMap; 2795 }; 2796 2797 self.getShortEndedElements = function() { 2798 return shortEndedElementsMap; 2799 }; 2800 2801 self.getSelfClosingElements = function() { 2802 return selfClosingElementsMap; 2803 }; 2804 2805 self.getNonEmptyElements = function() { 2806 return nonEmptyElementsMap; 2807 }; 2808 2809 self.getWhiteSpaceElements = function() { 2810 return whiteSpaceElementsMap; 2811 }; 2812 2813 self.isValidChild = function(name, child) { 2814 var parent = children[name]; 2815 2816 return !!(parent && parent[child]); 2817 }; 2818 2819 self.isValid = function(name, attr) { 2820 var attrPatterns, i, rule = getElementRule(name); 2821 2822 // Check if it's a valid element 2823 if (rule) { 2824 if (attr) { 2825 // Check if attribute name exists 2826 if (rule.attributes[attr]) { 2827 return true; 2828 } 2829 2830 // Check if attribute matches a regexp pattern 2831 attrPatterns = rule.attributePatterns; 2832 if (attrPatterns) { 2833 i = attrPatterns.length; 2834 while (i--) { 2835 if (attrPatterns[i].pattern.test(name)) { 2836 return true; 2837 } 2838 } 2839 } 2840 } else { 2841 return true; 2842 } 2843 } 2844 2845 // No match 2846 return false; 2847 }; 2848 2849 self.getElementRule = getElementRule; 2850 2851 self.getCustomElements = function() { 2852 return customElementsMap; 2853 }; 2854 2855 self.addValidElements = addValidElements; 2856 2857 self.setValidElements = setValidElements; 2858 2859 self.addCustomElements = addCustomElements; 2860 2861 self.addValidChildren = addValidChildren; 2862 }; 2863 })(tinymce); 2864 2865 (function(tinymce) { 2866 tinymce.html.SaxParser = function(settings, schema) { 2867 var self = this, noop = function() {}; 2868 2869 settings = settings || {}; 2870 self.schema = schema = schema || new tinymce.html.Schema(); 2871 2872 if (settings.fix_self_closing !== false) 2873 settings.fix_self_closing = true; 2874 2875 // Add handler functions from settings and setup default handlers 2876 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 2877 if (name) 2878 self[name] = settings[name] || noop; 2879 }); 2880 2881 self.parse = function(html) { 2882 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 2883 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 2884 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 2885 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 2886 2887 function processEndTag(name) { 2888 var pos, i; 2889 2890 // Find position of parent of the same type 2891 pos = stack.length; 2892 while (pos--) { 2893 if (stack[pos].name === name) 2894 break; 2895 } 2896 2897 // Found parent 2898 if (pos >= 0) { 2899 // Close all the open elements 2900 for (i = stack.length - 1; i >= pos; i--) { 2901 name = stack[i]; 2902 2903 if (name.valid) 2904 self.end(name.name); 2905 } 2906 2907 // Remove the open elements from the stack 2908 stack.length = pos; 2909 } 2910 }; 2911 2912 function parseAttribute(match, name, value, val2, val3) { 2913 var attrRule, i; 2914 2915 name = name.toLowerCase(); 2916 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 2917 2918 // Validate name and value 2919 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 2920 attrRule = validAttributesMap[name]; 2921 2922 // Find rule by pattern matching 2923 if (!attrRule && validAttributePatterns) { 2924 i = validAttributePatterns.length; 2925 while (i--) { 2926 attrRule = validAttributePatterns[i]; 2927 if (attrRule.pattern.test(name)) 2928 break; 2929 } 2930 2931 // No rule matched 2932 if (i === -1) 2933 attrRule = null; 2934 } 2935 2936 // No attribute rule found 2937 if (!attrRule) 2938 return; 2939 2940 // Validate value 2941 if (attrRule.validValues && !(value in attrRule.validValues)) 2942 return; 2943 } 2944 2945 // Add attribute to list and map 2946 attrList.map[name] = value; 2947 attrList.push({ 2948 name: name, 2949 value: value 2950 }); 2951 }; 2952 2953 // Precompile RegExps and map objects 2954 tokenRegExp = new RegExp('<(?:' + 2955 '(?:!--([\\w\\W]*?)-->)|' + // Comment 2956 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 2957 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 2958 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 2959 '(?:\\/([^>]+)>)|' + // End element 2960 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 2961 ')', 'g'); 2962 2963 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; 2964 specialElements = { 2965 'script' : /<\/script[^>]*>/gi, 2966 'style' : /<\/style[^>]*>/gi, 2967 'noscript' : /<\/noscript[^>]*>/gi 2968 }; 2969 2970 // Setup lookup tables for empty elements and boolean attributes 2971 shortEndedElements = schema.getShortEndedElements(); 2972 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 2973 fillAttrsMap = schema.getBoolAttrs(); 2974 validate = settings.validate; 2975 removeInternalElements = settings.remove_internals; 2976 fixSelfClosing = settings.fix_self_closing; 2977 isIE = tinymce.isIE; 2978 invalidPrefixRegExp = /^:/; 2979 2980 while (matches = tokenRegExp.exec(html)) { 2981 // Text 2982 if (index < matches.index) 2983 self.text(decode(html.substr(index, matches.index - index))); 2984 2985 if (value = matches[6]) { // End element 2986 value = value.toLowerCase(); 2987 2988 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 2989 if (isIE && invalidPrefixRegExp.test(value)) 2990 value = value.substr(1); 2991 2992 processEndTag(value); 2993 } else if (value = matches[7]) { // Start element 2994 value = value.toLowerCase(); 2995 2996 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 2997 if (isIE && invalidPrefixRegExp.test(value)) 2998 value = value.substr(1); 2999 3000 isShortEnded = value in shortEndedElements; 3001 3002 // Is self closing tag for example an <li> after an open <li> 3003 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3004 processEndTag(value); 3005 3006 // Validate element 3007 if (!validate || (elementRule = schema.getElementRule(value))) { 3008 isValidElement = true; 3009 3010 // Grab attributes map and patters when validation is enabled 3011 if (validate) { 3012 validAttributesMap = elementRule.attributes; 3013 validAttributePatterns = elementRule.attributePatterns; 3014 } 3015 3016 // Parse attributes 3017 if (attribsValue = matches[8]) { 3018 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3019 3020 // If the element has internal attributes then remove it if we are told to do so 3021 if (isInternalElement && removeInternalElements) 3022 isValidElement = false; 3023 3024 attrList = []; 3025 attrList.map = {}; 3026 3027 attribsValue.replace(attrRegExp, parseAttribute); 3028 } else { 3029 attrList = []; 3030 attrList.map = {}; 3031 } 3032 3033 // Process attributes if validation is enabled 3034 if (validate && !isInternalElement) { 3035 attributesRequired = elementRule.attributesRequired; 3036 attributesDefault = elementRule.attributesDefault; 3037 attributesForced = elementRule.attributesForced; 3038 3039 // Handle forced attributes 3040 if (attributesForced) { 3041 i = attributesForced.length; 3042 while (i--) { 3043 attr = attributesForced[i]; 3044 name = attr.name; 3045 attrValue = attr.value; 3046 3047 if (attrValue === '{$uid}') 3048 attrValue = 'mce_' + idCount++; 3049 3050 attrList.map[name] = attrValue; 3051 attrList.push({name: name, value: attrValue}); 3052 } 3053 } 3054 3055 // Handle default attributes 3056 if (attributesDefault) { 3057 i = attributesDefault.length; 3058 while (i--) { 3059 attr = attributesDefault[i]; 3060 name = attr.name; 3061 3062 if (!(name in attrList.map)) { 3063 attrValue = attr.value; 3064 3065 if (attrValue === '{$uid}') 3066 attrValue = 'mce_' + idCount++; 3067 3068 attrList.map[name] = attrValue; 3069 attrList.push({name: name, value: attrValue}); 3070 } 3071 } 3072 } 3073 3074 // Handle required attributes 3075 if (attributesRequired) { 3076 i = attributesRequired.length; 3077 while (i--) { 3078 if (attributesRequired[i] in attrList.map) 3079 break; 3080 } 3081 3082 // None of the required attributes where found 3083 if (i === -1) 3084 isValidElement = false; 3085 } 3086 3087 // Invalidate element if it's marked as bogus 3088 if (attrList.map['data-mce-bogus']) 3089 isValidElement = false; 3090 } 3091 3092 if (isValidElement) 3093 self.start(value, attrList, isShortEnded); 3094 } else 3095 isValidElement = false; 3096 3097 // Treat script, noscript and style a bit different since they may include code that looks like elements 3098 if (endRegExp = specialElements[value]) { 3099 endRegExp.lastIndex = index = matches.index + matches[0].length; 3100 3101 if (matches = endRegExp.exec(html)) { 3102 if (isValidElement) 3103 text = html.substr(index, matches.index - index); 3104 3105 index = matches.index + matches[0].length; 3106 } else { 3107 text = html.substr(index); 3108 index = html.length; 3109 } 3110 3111 if (isValidElement && text.length > 0) 3112 self.text(text, true); 3113 3114 if (isValidElement) 3115 self.end(value); 3116 3117 tokenRegExp.lastIndex = index; 3118 continue; 3119 } 3120 3121 // Push value on to stack 3122 if (!isShortEnded) { 3123 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3124 stack.push({name: value, valid: isValidElement}); 3125 else if (isValidElement) 3126 self.end(value); 3127 } 3128 } else if (value = matches[1]) { // Comment 3129 self.comment(value); 3130 } else if (value = matches[2]) { // CDATA 3131 self.cdata(value); 3132 } else if (value = matches[3]) { // DOCTYPE 3133 self.doctype(value); 3134 } else if (value = matches[4]) { // PI 3135 self.pi(value, matches[5]); 3136 } 3137 3138 index = matches.index + matches[0].length; 3139 } 3140 3141 // Text 3142 if (index < html.length) 3143 self.text(decode(html.substr(index))); 3144 3145 // Close any open elements 3146 for (i = stack.length - 1; i >= 0; i--) { 3147 value = stack[i]; 3148 3149 if (value.valid) 3150 self.end(value.name); 3151 } 3152 }; 3153 } 3154 })(tinymce); 3155 3156 (function(tinymce) { 3157 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3158 '#text' : 3, 3159 '#comment' : 8, 3160 '#cdata' : 4, 3161 '#pi' : 7, 3162 '#doctype' : 10, 3163 '#document-fragment' : 11 3164 }; 3165 3166 // Walks the tree left/right 3167 function walk(node, root_node, prev) { 3168 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3169 3170 // Walk into nodes if it has a start 3171 if (node[startName]) 3172 return node[startName]; 3173 3174 // Return the sibling if it has one 3175 if (node !== root_node) { 3176 sibling = node[siblingName]; 3177 3178 if (sibling) 3179 return sibling; 3180 3181 // Walk up the parents to look for siblings 3182 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3183 sibling = parent[siblingName]; 3184 3185 if (sibling) 3186 return sibling; 3187 } 3188 } 3189 }; 3190 3191 function Node(name, type) { 3192 this.name = name; 3193 this.type = type; 3194 3195 if (type === 1) { 3196 this.attributes = []; 3197 this.attributes.map = {}; 3198 } 3199 } 3200 3201 tinymce.extend(Node.prototype, { 3202 replace : function(node) { 3203 var self = this; 3204 3205 if (node.parent) 3206 node.remove(); 3207 3208 self.insert(node, self); 3209 self.remove(); 3210 3211 return self; 3212 }, 3213 3214 attr : function(name, value) { 3215 var self = this, attrs, i, undef; 3216 3217 if (typeof name !== "string") { 3218 for (i in name) 3219 self.attr(i, name[i]); 3220 3221 return self; 3222 } 3223 3224 if (attrs = self.attributes) { 3225 if (value !== undef) { 3226 // Remove attribute 3227 if (value === null) { 3228 if (name in attrs.map) { 3229 delete attrs.map[name]; 3230 3231 i = attrs.length; 3232 while (i--) { 3233 if (attrs[i].name === name) { 3234 attrs = attrs.splice(i, 1); 3235 return self; 3236 } 3237 } 3238 } 3239 3240 return self; 3241 } 3242 3243 // Set attribute 3244 if (name in attrs.map) { 3245 // Set attribute 3246 i = attrs.length; 3247 while (i--) { 3248 if (attrs[i].name === name) { 3249 attrs[i].value = value; 3250 break; 3251 } 3252 } 3253 } else 3254 attrs.push({name: name, value: value}); 3255 3256 attrs.map[name] = value; 3257 3258 return self; 3259 } else { 3260 return attrs.map[name]; 3261 } 3262 } 3263 }, 3264 3265 clone : function() { 3266 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3267 3268 // Clone element attributes 3269 if (selfAttrs = self.attributes) { 3270 cloneAttrs = []; 3271 cloneAttrs.map = {}; 3272 3273 for (i = 0, l = selfAttrs.length; i < l; i++) { 3274 selfAttr = selfAttrs[i]; 3275 3276 // Clone everything except id 3277 if (selfAttr.name !== 'id') { 3278 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3279 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3280 } 3281 } 3282 3283 clone.attributes = cloneAttrs; 3284 } 3285 3286 clone.value = self.value; 3287 clone.shortEnded = self.shortEnded; 3288 3289 return clone; 3290 }, 3291 3292 wrap : function(wrapper) { 3293 var self = this; 3294 3295 self.parent.insert(wrapper, self); 3296 wrapper.append(self); 3297 3298 return self; 3299 }, 3300 3301 unwrap : function() { 3302 var self = this, node, next; 3303 3304 for (node = self.firstChild; node; ) { 3305 next = node.next; 3306 self.insert(node, self, true); 3307 node = next; 3308 } 3309 3310 self.remove(); 3311 }, 3312 3313 remove : function() { 3314 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3315 3316 if (parent) { 3317 if (parent.firstChild === self) { 3318 parent.firstChild = next; 3319 3320 if (next) 3321 next.prev = null; 3322 } else { 3323 prev.next = next; 3324 } 3325 3326 if (parent.lastChild === self) { 3327 parent.lastChild = prev; 3328 3329 if (prev) 3330 prev.next = null; 3331 } else { 3332 next.prev = prev; 3333 } 3334 3335 self.parent = self.next = self.prev = null; 3336 } 3337 3338 return self; 3339 }, 3340 3341 append : function(node) { 3342 var self = this, last; 3343 3344 if (node.parent) 3345 node.remove(); 3346 3347 last = self.lastChild; 3348 if (last) { 3349 last.next = node; 3350 node.prev = last; 3351 self.lastChild = node; 3352 } else 3353 self.lastChild = self.firstChild = node; 3354 3355 node.parent = self; 3356 3357 return node; 3358 }, 3359 3360 insert : function(node, ref_node, before) { 3361 var parent; 3362 3363 if (node.parent) 3364 node.remove(); 3365 3366 parent = ref_node.parent || this; 3367 3368 if (before) { 3369 if (ref_node === parent.firstChild) 3370 parent.firstChild = node; 3371 else 3372 ref_node.prev.next = node; 3373 3374 node.prev = ref_node.prev; 3375 node.next = ref_node; 3376 ref_node.prev = node; 3377 } else { 3378 if (ref_node === parent.lastChild) 3379 parent.lastChild = node; 3380 else 3381 ref_node.next.prev = node; 3382 3383 node.next = ref_node.next; 3384 node.prev = ref_node; 3385 ref_node.next = node; 3386 } 3387 3388 node.parent = parent; 3389 3390 return node; 3391 }, 3392 3393 getAll : function(name) { 3394 var self = this, node, collection = []; 3395 3396 for (node = self.firstChild; node; node = walk(node, self)) { 3397 if (node.name === name) 3398 collection.push(node); 3399 } 3400 3401 return collection; 3402 }, 3403 3404 empty : function() { 3405 var self = this, nodes, i, node; 3406 3407 // Remove all children 3408 if (self.firstChild) { 3409 nodes = []; 3410 3411 // Collect the children 3412 for (node = self.firstChild; node; node = walk(node, self)) 3413 nodes.push(node); 3414 3415 // Remove the children 3416 i = nodes.length; 3417 while (i--) { 3418 node = nodes[i]; 3419 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3420 } 3421 } 3422 3423 self.firstChild = self.lastChild = null; 3424 3425 return self; 3426 }, 3427 3428 isEmpty : function(elements) { 3429 var self = this, node = self.firstChild, i, name; 3430 3431 if (node) { 3432 do { 3433 if (node.type === 1) { 3434 // Ignore bogus elements 3435 if (node.attributes.map['data-mce-bogus']) 3436 continue; 3437 3438 // Keep empty elements like <img /> 3439 if (elements[node.name]) 3440 return false; 3441 3442 // Keep elements with data attributes or name attribute like <a name="1"></a> 3443 i = node.attributes.length; 3444 while (i--) { 3445 name = node.attributes[i].name; 3446 if (name === "name" || name.indexOf('data-') === 0) 3447 return false; 3448 } 3449 } 3450 3451 // Keep comments 3452 if (node.type === 8) 3453 return false; 3454 3455 // Keep non whitespace text nodes 3456 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3457 return false; 3458 } while (node = walk(node, self)); 3459 } 3460 3461 return true; 3462 }, 3463 3464 walk : function(prev) { 3465 return walk(this, null, prev); 3466 } 3467 }); 3468 3469 tinymce.extend(Node, { 3470 create : function(name, attrs) { 3471 var node, attrName; 3472 3473 // Create node 3474 node = new Node(name, typeLookup[name] || 1); 3475 3476 // Add attributes if needed 3477 if (attrs) { 3478 for (attrName in attrs) 3479 node.attr(attrName, attrs[attrName]); 3480 } 3481 3482 return node; 3483 } 3484 }); 3485 3486 tinymce.html.Node = Node; 3487 })(tinymce); 3488 3489 (function(tinymce) { 3490 var Node = tinymce.html.Node; 3491 3492 tinymce.html.DomParser = function(settings, schema) { 3493 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3494 3495 settings = settings || {}; 3496 settings.validate = "validate" in settings ? settings.validate : true; 3497 settings.root_name = settings.root_name || 'body'; 3498 self.schema = schema = schema || new tinymce.html.Schema(); 3499 3500 function fixInvalidChildren(nodes) { 3501 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3502 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; 3503 3504 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3505 nonEmptyElements = schema.getNonEmptyElements(); 3506 3507 for (ni = 0; ni < nodes.length; ni++) { 3508 node = nodes[ni]; 3509 3510 // Already removed 3511 if (!node.parent) 3512 continue; 3513 3514 // Get list of all parent nodes until we find a valid parent to stick the child into 3515 parents = [node]; 3516 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3517 parents.push(parent); 3518 3519 // Found a suitable parent 3520 if (parent && parents.length > 1) { 3521 // Reverse the array since it makes looping easier 3522 parents.reverse(); 3523 3524 // Clone the related parent and insert that after the moved node 3525 newParent = currentNode = self.filterNode(parents[0].clone()); 3526 3527 // Start cloning and moving children on the left side of the target node 3528 for (i = 0; i < parents.length - 1; i++) { 3529 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3530 tempNode = self.filterNode(parents[i].clone()); 3531 currentNode.append(tempNode); 3532 } else 3533 tempNode = currentNode; 3534 3535 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3536 nextNode = childNode.next; 3537 tempNode.append(childNode); 3538 childNode = nextNode; 3539 } 3540 3541 currentNode = tempNode; 3542 } 3543 3544 if (!newParent.isEmpty(nonEmptyElements)) { 3545 parent.insert(newParent, parents[0], true); 3546 parent.insert(node, newParent); 3547 } else { 3548 parent.insert(node, parents[0], true); 3549 } 3550 3551 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3552 parent = parents[0]; 3553 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3554 parent.empty().remove(); 3555 } 3556 } else if (node.parent) { 3557 // If it's an LI try to find a UL/OL for it or wrap it 3558 if (node.name === 'li') { 3559 sibling = node.prev; 3560 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3561 sibling.append(node); 3562 continue; 3563 } 3564 3565 sibling = node.next; 3566 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3567 sibling.insert(node, sibling.firstChild, true); 3568 continue; 3569 } 3570 3571 node.wrap(self.filterNode(new Node('ul', 1))); 3572 continue; 3573 } 3574 3575 // Try wrapping the element in a DIV 3576 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3577 node.wrap(self.filterNode(new Node('div', 1))); 3578 } else { 3579 // We failed wrapping it, then remove or unwrap it 3580 if (node.name === 'style' || node.name === 'script') 3581 node.empty().remove(); 3582 else 3583 node.unwrap(); 3584 } 3585 } 3586 } 3587 }; 3588 3589 self.filterNode = function(node) { 3590 var i, name, list; 3591 3592 // Run element filters 3593 if (name in nodeFilters) { 3594 list = matchedNodes[name]; 3595 3596 if (list) 3597 list.push(node); 3598 else 3599 matchedNodes[name] = [node]; 3600 } 3601 3602 // Run attribute filters 3603 i = attributeFilters.length; 3604 while (i--) { 3605 name = attributeFilters[i].name; 3606 3607 if (name in node.attributes.map) { 3608 list = matchedAttributes[name]; 3609 3610 if (list) 3611 list.push(node); 3612 else 3613 matchedAttributes[name] = [node]; 3614 } 3615 } 3616 3617 return node; 3618 }; 3619 3620 self.addNodeFilter = function(name, callback) { 3621 tinymce.each(tinymce.explode(name), function(name) { 3622 var list = nodeFilters[name]; 3623 3624 if (!list) 3625 nodeFilters[name] = list = []; 3626 3627 list.push(callback); 3628 }); 3629 }; 3630 3631 self.addAttributeFilter = function(name, callback) { 3632 tinymce.each(tinymce.explode(name), function(name) { 3633 var i; 3634 3635 for (i = 0; i < attributeFilters.length; i++) { 3636 if (attributeFilters[i].name === name) { 3637 attributeFilters[i].callbacks.push(callback); 3638 return; 3639 } 3640 } 3641 3642 attributeFilters.push({name: name, callbacks: [callback]}); 3643 }); 3644 }; 3645 3646 self.parse = function(html, args) { 3647 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3648 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3649 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3650 3651 args = args || {}; 3652 matchedNodes = {}; 3653 matchedAttributes = {}; 3654 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3655 nonEmptyElements = schema.getNonEmptyElements(); 3656 children = schema.children; 3657 validate = settings.validate; 3658 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3659 3660 whiteSpaceElements = schema.getWhiteSpaceElements(); 3661 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3662 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3663 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3664 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3665 3666 function addRootBlocks() { 3667 var node = rootNode.firstChild, next, rootBlockNode; 3668 3669 while (node) { 3670 next = node.next; 3671 3672 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3673 if (!rootBlockNode) { 3674 // Create a new root block element 3675 rootBlockNode = createNode(rootBlockName, 1); 3676 rootNode.insert(rootBlockNode, node); 3677 rootBlockNode.append(node); 3678 } else 3679 rootBlockNode.append(node); 3680 } else { 3681 rootBlockNode = null; 3682 } 3683 3684 node = next; 3685 }; 3686 }; 3687 3688 function createNode(name, type) { 3689 var node = new Node(name, type), list; 3690 3691 if (name in nodeFilters) { 3692 list = matchedNodes[name]; 3693 3694 if (list) 3695 list.push(node); 3696 else 3697 matchedNodes[name] = [node]; 3698 } 3699 3700 return node; 3701 }; 3702 3703 function removeWhitespaceBefore(node) { 3704 var textNode, textVal, sibling; 3705 3706 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3707 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3708 3709 if (textVal.length > 0) { 3710 textNode.value = textVal; 3711 textNode = textNode.prev; 3712 } else { 3713 sibling = textNode.prev; 3714 textNode.remove(); 3715 textNode = sibling; 3716 } 3717 } 3718 }; 3719 3720 function cloneAndExcludeBlocks(input) { 3721 var name, output = {}; 3722 3723 for (name in input) { 3724 if (name !== 'li' && name != 'p') { 3725 output[name] = input[name]; 3726 } 3727 } 3728 3729 return output; 3730 }; 3731 3732 parser = new tinymce.html.SaxParser({ 3733 validate : validate, 3734 3735 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 3736 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 3737 3738 cdata: function(text) { 3739 node.append(createNode('#cdata', 4)).value = text; 3740 }, 3741 3742 text: function(text, raw) { 3743 var textNode; 3744 3745 // Trim all redundant whitespace on non white space elements 3746 if (!isInWhiteSpacePreservedElement) { 3747 text = text.replace(allWhiteSpaceRegExp, ' '); 3748 3749 if (node.lastChild && blockElements[node.lastChild.name]) 3750 text = text.replace(startWhiteSpaceRegExp, ''); 3751 } 3752 3753 // Do we need to create the node 3754 if (text.length !== 0) { 3755 textNode = createNode('#text', 3); 3756 textNode.raw = !!raw; 3757 node.append(textNode).value = text; 3758 } 3759 }, 3760 3761 comment: function(text) { 3762 node.append(createNode('#comment', 8)).value = text; 3763 }, 3764 3765 pi: function(name, text) { 3766 node.append(createNode(name, 7)).value = text; 3767 removeWhitespaceBefore(node); 3768 }, 3769 3770 doctype: function(text) { 3771 var newNode; 3772 3773 newNode = node.append(createNode('#doctype', 10)); 3774 newNode.value = text; 3775 removeWhitespaceBefore(node); 3776 }, 3777 3778 start: function(name, attrs, empty) { 3779 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 3780 3781 elementRule = validate ? schema.getElementRule(name) : {}; 3782 if (elementRule) { 3783 newNode = createNode(elementRule.outputName || name, 1); 3784 newNode.attributes = attrs; 3785 newNode.shortEnded = empty; 3786 3787 node.append(newNode); 3788 3789 // Check if node is valid child of the parent node is the child is 3790 // unknown we don't collect it since it's probably a custom element 3791 parent = children[node.name]; 3792 if (parent && children[newNode.name] && !parent[newNode.name]) 3793 invalidChildren.push(newNode); 3794 3795 attrFiltersLen = attributeFilters.length; 3796 while (attrFiltersLen--) { 3797 attrName = attributeFilters[attrFiltersLen].name; 3798 3799 if (attrName in attrs.map) { 3800 list = matchedAttributes[attrName]; 3801 3802 if (list) 3803 list.push(newNode); 3804 else 3805 matchedAttributes[attrName] = [newNode]; 3806 } 3807 } 3808 3809 // Trim whitespace before block 3810 if (blockElements[name]) 3811 removeWhitespaceBefore(newNode); 3812 3813 // Change current node if the element wasn't empty i.e not <br /> or <img /> 3814 if (!empty) 3815 node = newNode; 3816 3817 // Check if we are inside a whitespace preserved element 3818 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3819 isInWhiteSpacePreservedElement = true; 3820 } 3821 } 3822 }, 3823 3824 end: function(name) { 3825 var textNode, elementRule, text, sibling, tempNode; 3826 3827 elementRule = validate ? schema.getElementRule(name) : {}; 3828 if (elementRule) { 3829 if (blockElements[name]) { 3830 if (!isInWhiteSpacePreservedElement) { 3831 // Trim whitespace of the first node in a block 3832 textNode = node.firstChild; 3833 if (textNode && textNode.type === 3) { 3834 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3835 3836 // Any characters left after trim or should we remove it 3837 if (text.length > 0) { 3838 textNode.value = text; 3839 textNode = textNode.next; 3840 } else { 3841 sibling = textNode.next; 3842 textNode.remove(); 3843 textNode = sibling; 3844 } 3845 3846 // Remove any pure whitespace siblings 3847 while (textNode && textNode.type === 3) { 3848 text = textNode.value; 3849 sibling = textNode.next; 3850 3851 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3852 textNode.remove(); 3853 textNode = sibling; 3854 } 3855 3856 textNode = sibling; 3857 } 3858 } 3859 3860 // Trim whitespace of the last node in a block 3861 textNode = node.lastChild; 3862 if (textNode && textNode.type === 3) { 3863 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 3864 3865 // Any characters left after trim or should we remove it 3866 if (text.length > 0) { 3867 textNode.value = text; 3868 textNode = textNode.prev; 3869 } else { 3870 sibling = textNode.prev; 3871 textNode.remove(); 3872 textNode = sibling; 3873 } 3874 3875 // Remove any pure whitespace siblings 3876 while (textNode && textNode.type === 3) { 3877 text = textNode.value; 3878 sibling = textNode.prev; 3879 3880 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3881 textNode.remove(); 3882 textNode = sibling; 3883 } 3884 3885 textNode = sibling; 3886 } 3887 } 3888 } 3889 3890 // Trim start white space 3891 textNode = node.prev; 3892 if (textNode && textNode.type === 3) { 3893 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3894 3895 if (text.length > 0) 3896 textNode.value = text; 3897 else 3898 textNode.remove(); 3899 } 3900 } 3901 3902 // Check if we exited a whitespace preserved element 3903 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3904 isInWhiteSpacePreservedElement = false; 3905 } 3906 3907 // Handle empty nodes 3908 if (elementRule.removeEmpty || elementRule.paddEmpty) { 3909 if (node.isEmpty(nonEmptyElements)) { 3910 if (elementRule.paddEmpty) 3911 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 3912 else { 3913 // Leave nodes that have a name like <a name="name"> 3914 if (!node.attributes.map.name && !node.attributes.map.id) { 3915 tempNode = node.parent; 3916 node.empty().remove(); 3917 node = tempNode; 3918 return; 3919 } 3920 } 3921 } 3922 } 3923 3924 node = node.parent; 3925 } 3926 } 3927 }, schema); 3928 3929 rootNode = node = new Node(args.context || settings.root_name, 11); 3930 3931 parser.parse(html); 3932 3933 // Fix invalid children or report invalid children in a contextual parsing 3934 if (validate && invalidChildren.length) { 3935 if (!args.context) 3936 fixInvalidChildren(invalidChildren); 3937 else 3938 args.invalid = true; 3939 } 3940 3941 // Wrap nodes in the root into block elements if the root is body 3942 if (rootBlockName && rootNode.name == 'body') 3943 addRootBlocks(); 3944 3945 // Run filters only when the contents is valid 3946 if (!args.invalid) { 3947 // Run node filters 3948 for (name in matchedNodes) { 3949 list = nodeFilters[name]; 3950 nodes = matchedNodes[name]; 3951 3952 // Remove already removed children 3953 fi = nodes.length; 3954 while (fi--) { 3955 if (!nodes[fi].parent) 3956 nodes.splice(fi, 1); 3957 } 3958 3959 for (i = 0, l = list.length; i < l; i++) 3960 list[i](nodes, name, args); 3961 } 3962 3963 // Run attribute filters 3964 for (i = 0, l = attributeFilters.length; i < l; i++) { 3965 list = attributeFilters[i]; 3966 3967 if (list.name in matchedAttributes) { 3968 nodes = matchedAttributes[list.name]; 3969 3970 // Remove already removed children 3971 fi = nodes.length; 3972 while (fi--) { 3973 if (!nodes[fi].parent) 3974 nodes.splice(fi, 1); 3975 } 3976 3977 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 3978 list.callbacks[fi](nodes, list.name, args); 3979 } 3980 } 3981 } 3982 3983 return rootNode; 3984 }; 3985 3986 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 3987 // make it possible to place the caret inside empty blocks. This logic tries to remove 3988 // these elements and keep br elements that where intended to be there intact 3989 if (settings.remove_trailing_brs) { 3990 self.addNodeFilter('br', function(nodes, name) { 3991 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 3992 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 3993 3994 // Remove brs from body element as well 3995 blockElements.body = 1; 3996 3997 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 3998 for (i = 0; i < l; i++) { 3999 node = nodes[i]; 4000 parent = node.parent; 4001 4002 if (blockElements[node.parent.name] && node === parent.lastChild) { 4003 // Loop all nodes to the left of the current node and check for other BR elements 4004 // excluding bookmarks since they are invisible 4005 prev = node.prev; 4006 while (prev) { 4007 prevName = prev.name; 4008 4009 // Ignore bookmarks 4010 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4011 // Found a non BR element 4012 if (prevName !== "br") 4013 break; 4014 4015 // Found another br it's a <br><br> structure then don't remove anything 4016 if (prevName === 'br') { 4017 node = null; 4018 break; 4019 } 4020 } 4021 4022 prev = prev.prev; 4023 } 4024 4025 if (node) { 4026 node.remove(); 4027 4028 // Is the parent to be considered empty after we removed the BR 4029 if (parent.isEmpty(nonEmptyElements)) { 4030 elementRule = schema.getElementRule(parent.name); 4031 4032 // Remove or padd the element depending on schema rule 4033 if (elementRule) { 4034 if (elementRule.removeEmpty) 4035 parent.remove(); 4036 else if (elementRule.paddEmpty) 4037 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4038 } 4039 } 4040 } 4041 } else { 4042 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4043 lastParent = node; 4044 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4045 lastParent = parent; 4046 4047 if (blockElements[parent.name]) { 4048 break; 4049 } 4050 4051 parent = parent.parent; 4052 } 4053 4054 if (lastParent === parent) { 4055 textNode = new tinymce.html.Node('#text', 3); 4056 textNode.value = '\u00a0'; 4057 node.replace(textNode); 4058 } 4059 } 4060 } 4061 }); 4062 } 4063 4064 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4065 if (!settings.allow_html_in_named_anchor) { 4066 self.addAttributeFilter('id,name', function(nodes, name) { 4067 var i = nodes.length, sibling, prevSibling, parent, node; 4068 4069 while (i--) { 4070 node = nodes[i]; 4071 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4072 parent = node.parent; 4073 4074 // Move children after current node 4075 sibling = node.lastChild; 4076 do { 4077 prevSibling = sibling.prev; 4078 parent.insert(sibling, node); 4079 sibling = prevSibling; 4080 } while (sibling); 4081 } 4082 } 4083 }); 4084 } 4085 } 4086 })(tinymce); 4087 4088 tinymce.html.Writer = function(settings) { 4089 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4090 4091 settings = settings || {}; 4092 indent = settings.indent; 4093 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4094 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4095 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4096 htmlOutput = settings.element_format == "html"; 4097 4098 return { 4099 start: function(name, attrs, empty) { 4100 var i, l, attr, value; 4101 4102 if (indent && indentBefore[name] && html.length > 0) { 4103 value = html[html.length - 1]; 4104 4105 if (value.length > 0 && value !== '\n') 4106 html.push('\n'); 4107 } 4108 4109 html.push('<', name); 4110 4111 if (attrs) { 4112 for (i = 0, l = attrs.length; i < l; i++) { 4113 attr = attrs[i]; 4114 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4115 } 4116 } 4117 4118 if (!empty || htmlOutput) 4119 html[html.length] = '>'; 4120 else 4121 html[html.length] = ' />'; 4122 4123 if (empty && indent && indentAfter[name] && html.length > 0) { 4124 value = html[html.length - 1]; 4125 4126 if (value.length > 0 && value !== '\n') 4127 html.push('\n'); 4128 } 4129 }, 4130 4131 end: function(name) { 4132 var value; 4133 4134 /*if (indent && indentBefore[name] && html.length > 0) { 4135 value = html[html.length - 1]; 4136 4137 if (value.length > 0 && value !== '\n') 4138 html.push('\n'); 4139 }*/ 4140 4141 html.push('</', name, '>'); 4142 4143 if (indent && indentAfter[name] && html.length > 0) { 4144 value = html[html.length - 1]; 4145 4146 if (value.length > 0 && value !== '\n') 4147 html.push('\n'); 4148 } 4149 }, 4150 4151 text: function(text, raw) { 4152 if (text.length > 0) 4153 html[html.length] = raw ? text : encode(text); 4154 }, 4155 4156 cdata: function(text) { 4157 html.push('<![CDATA[', text, ']]>'); 4158 }, 4159 4160 comment: function(text) { 4161 html.push('<!--', text, '-->'); 4162 }, 4163 4164 pi: function(name, text) { 4165 if (text) 4166 html.push('<?', name, ' ', text, '?>'); 4167 else 4168 html.push('<?', name, '?>'); 4169 4170 if (indent) 4171 html.push('\n'); 4172 }, 4173 4174 doctype: function(text) { 4175 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4176 }, 4177 4178 reset: function() { 4179 html.length = 0; 4180 }, 4181 4182 getContent: function() { 4183 return html.join('').replace(/\n$/, ''); 4184 } 4185 }; 4186 }; 4187 4188 (function(tinymce) { 4189 tinymce.html.Serializer = function(settings, schema) { 4190 var self = this, writer = new tinymce.html.Writer(settings); 4191 4192 settings = settings || {}; 4193 settings.validate = "validate" in settings ? settings.validate : true; 4194 4195 self.schema = schema = schema || new tinymce.html.Schema(); 4196 self.writer = writer; 4197 4198 self.serialize = function(node) { 4199 var handlers, validate; 4200 4201 validate = settings.validate; 4202 4203 handlers = { 4204 // #text 4205 3: function(node, raw) { 4206 writer.text(node.value, node.raw); 4207 }, 4208 4209 // #comment 4210 8: function(node) { 4211 writer.comment(node.value); 4212 }, 4213 4214 // Processing instruction 4215 7: function(node) { 4216 writer.pi(node.name, node.value); 4217 }, 4218 4219 // Doctype 4220 10: function(node) { 4221 writer.doctype(node.value); 4222 }, 4223 4224 // CDATA 4225 4: function(node) { 4226 writer.cdata(node.value); 4227 }, 4228 4229 // Document fragment 4230 11: function(node) { 4231 if ((node = node.firstChild)) { 4232 do { 4233 walk(node); 4234 } while (node = node.next); 4235 } 4236 } 4237 }; 4238 4239 writer.reset(); 4240 4241 function walk(node) { 4242 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4243 4244 if (!handler) { 4245 name = node.name; 4246 isEmpty = node.shortEnded; 4247 attrs = node.attributes; 4248 4249 // Sort attributes 4250 if (validate && attrs && attrs.length > 1) { 4251 sortedAttrs = []; 4252 sortedAttrs.map = {}; 4253 4254 elementRule = schema.getElementRule(node.name); 4255 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4256 attrName = elementRule.attributesOrder[i]; 4257 4258 if (attrName in attrs.map) { 4259 attrValue = attrs.map[attrName]; 4260 sortedAttrs.map[attrName] = attrValue; 4261 sortedAttrs.push({name: attrName, value: attrValue}); 4262 } 4263 } 4264 4265 for (i = 0, l = attrs.length; i < l; i++) { 4266 attrName = attrs[i].name; 4267 4268 if (!(attrName in sortedAttrs.map)) { 4269 attrValue = attrs.map[attrName]; 4270 sortedAttrs.map[attrName] = attrValue; 4271 sortedAttrs.push({name: attrName, value: attrValue}); 4272 } 4273 } 4274 4275 attrs = sortedAttrs; 4276 } 4277 4278 writer.start(node.name, attrs, isEmpty); 4279 4280 if (!isEmpty) { 4281 if ((node = node.firstChild)) { 4282 do { 4283 walk(node); 4284 } while (node = node.next); 4285 } 4286 4287 writer.end(name); 4288 } 4289 } else 4290 handler(node); 4291 } 4292 4293 // Serialize element and treat all non elements as fragments 4294 if (node.type == 1 && !settings.inner) 4295 walk(node); 4296 else 4297 handlers[11](node); 4298 4299 return writer.getContent(); 4300 }; 4301 } 4302 })(tinymce); 4303 4304 // JSLint defined globals 4305 /*global tinymce:false, window:false */ 4306 4307 tinymce.dom = {}; 4308 4309 (function(namespace, expando) { 4310 var w3cEventModel = !!document.addEventListener; 4311 4312 function addEvent(target, name, callback, capture) { 4313 if (target.addEventListener) { 4314 target.addEventListener(name, callback, capture || false); 4315 } else if (target.attachEvent) { 4316 target.attachEvent('on' + name, callback); 4317 } 4318 } 4319 4320 function removeEvent(target, name, callback, capture) { 4321 if (target.removeEventListener) { 4322 target.removeEventListener(name, callback, capture || false); 4323 } else if (target.detachEvent) { 4324 target.detachEvent('on' + name, callback); 4325 } 4326 } 4327 4328 function fix(original_event, data) { 4329 var name, event = data || {}; 4330 4331 // Dummy function that gets replaced on the delegation state functions 4332 function returnFalse() { 4333 return false; 4334 } 4335 4336 // Dummy function that gets replaced on the delegation state functions 4337 function returnTrue() { 4338 return true; 4339 } 4340 4341 // Copy all properties from the original event 4342 for (name in original_event) { 4343 // layerX/layerY is deprecated in Chrome and produces a warning 4344 if (name !== "layerX" && name !== "layerY") { 4345 event[name] = original_event[name]; 4346 } 4347 } 4348 4349 // Normalize target IE uses srcElement 4350 if (!event.target) { 4351 event.target = event.srcElement || document; 4352 } 4353 4354 // Add preventDefault method 4355 event.preventDefault = function() { 4356 event.isDefaultPrevented = returnTrue; 4357 4358 // Execute preventDefault on the original event object 4359 if (original_event) { 4360 if (original_event.preventDefault) { 4361 original_event.preventDefault(); 4362 } else { 4363 original_event.returnValue = false; // IE 4364 } 4365 } 4366 }; 4367 4368 // Add stopPropagation 4369 event.stopPropagation = function() { 4370 event.isPropagationStopped = returnTrue; 4371 4372 // Execute stopPropagation on the original event object 4373 if (original_event) { 4374 if (original_event.stopPropagation) { 4375 original_event.stopPropagation(); 4376 } else { 4377 original_event.cancelBubble = true; // IE 4378 } 4379 } 4380 }; 4381 4382 // Add stopImmediatePropagation 4383 event.stopImmediatePropagation = function() { 4384 event.isImmediatePropagationStopped = returnTrue; 4385 event.stopPropagation(); 4386 }; 4387 4388 // Add event delegation states 4389 if (!event.isDefaultPrevented) { 4390 event.isDefaultPrevented = returnFalse; 4391 event.isPropagationStopped = returnFalse; 4392 event.isImmediatePropagationStopped = returnFalse; 4393 } 4394 4395 return event; 4396 } 4397 4398 function bindOnReady(win, callback, event_utils) { 4399 var doc = win.document, event = {type: 'ready'}; 4400 4401 // Gets called when the DOM is ready 4402 function readyHandler() { 4403 if (!event_utils.domLoaded) { 4404 event_utils.domLoaded = true; 4405 callback(event); 4406 } 4407 } 4408 4409 // Use W3C method 4410 if (w3cEventModel) { 4411 addEvent(win, 'DOMContentLoaded', readyHandler); 4412 } else { 4413 // Use IE method 4414 addEvent(doc, "readystatechange", function() { 4415 if (doc.readyState === "complete") { 4416 removeEvent(doc, "readystatechange", arguments.callee); 4417 readyHandler(); 4418 } 4419 }); 4420 4421 // Wait until we can scroll, when we can the DOM is initialized 4422 if (doc.documentElement.doScroll && win === win.top) { 4423 (function() { 4424 try { 4425 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4426 // http://javascript.nwbox.com/IEContentLoaded/ 4427 doc.documentElement.doScroll("left"); 4428 } catch (ex) { 4429 setTimeout(arguments.callee, 0); 4430 return; 4431 } 4432 4433 readyHandler(); 4434 })(); 4435 } 4436 } 4437 4438 // Fallback if any of the above methods should fail for some odd reason 4439 addEvent(win, 'load', readyHandler); 4440 } 4441 4442 function EventUtils(proxy) { 4443 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4444 4445 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4446 hasFocusIn = "onfocusin" in document.documentElement; 4447 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4448 count = 1; 4449 4450 // State if the DOMContentLoaded was executed or not 4451 self.domLoaded = false; 4452 self.events = events; 4453 4454 function executeHandlers(evt, id) { 4455 var callbackList, i, l, callback; 4456 4457 callbackList = events[id][evt.type]; 4458 if (callbackList) { 4459 for (i = 0, l = callbackList.length; i < l; i++) { 4460 callback = callbackList[i]; 4461 4462 // Check if callback exists might be removed if a unbind is called inside the callback 4463 if (callback && callback.func.call(callback.scope, evt) === false) { 4464 evt.preventDefault(); 4465 } 4466 4467 // Should we stop propagation to immediate listeners 4468 if (evt.isImmediatePropagationStopped()) { 4469 return; 4470 } 4471 } 4472 } 4473 } 4474 4475 self.bind = function(target, names, callback, scope) { 4476 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4477 4478 // Native event handler function patches the event and executes the callbacks for the expando 4479 function defaultNativeHandler(evt) { 4480 executeHandlers(fix(evt || win.event), id); 4481 } 4482 4483 // Don't bind to text nodes or comments 4484 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4485 return; 4486 } 4487 4488 // Create or get events id for the target 4489 if (!target[expando]) { 4490 id = count++; 4491 target[expando] = id; 4492 events[id] = {}; 4493 } else { 4494 id = target[expando]; 4495 4496 if (!events[id]) { 4497 events[id] = {}; 4498 } 4499 } 4500 4501 // Setup the specified scope or use the target as a default 4502 scope = scope || target; 4503 4504 // Split names and bind each event, enables you to bind multiple events with one call 4505 names = names.split(' '); 4506 i = names.length; 4507 while (i--) { 4508 name = names[i]; 4509 nativeHandler = defaultNativeHandler; 4510 fakeName = capture = false; 4511 4512 // Use ready instead of DOMContentLoaded 4513 if (name === "DOMContentLoaded") { 4514 name = "ready"; 4515 } 4516 4517 // DOM is already ready 4518 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4519 self.domLoaded = true; 4520 callback.call(scope, fix({type: name})); 4521 continue; 4522 } 4523 4524 // Handle mouseenter/mouseleaver 4525 if (!hasMouseEnterLeave) { 4526 fakeName = mouseEnterLeave[name]; 4527 4528 if (fakeName) { 4529 nativeHandler = function(evt) { 4530 var current, related; 4531 4532 current = evt.currentTarget; 4533 related = evt.relatedTarget; 4534 4535 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4536 if (related && current.contains) { 4537 // Use contains for performance 4538 related = current.contains(related); 4539 } else { 4540 while (related && related !== current) { 4541 related = related.parentNode; 4542 } 4543 } 4544 4545 // Fire fake event 4546 if (!related) { 4547 evt = fix(evt || win.event); 4548 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4549 evt.target = current; 4550 executeHandlers(evt, id); 4551 } 4552 }; 4553 } 4554 } 4555 4556 // Fake bubbeling of focusin/focusout 4557 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4558 capture = true; 4559 fakeName = name === "focusin" ? "focus" : "blur"; 4560 nativeHandler = function(evt) { 4561 evt = fix(evt || win.event); 4562 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4563 executeHandlers(evt, id); 4564 }; 4565 } 4566 4567 // Setup callback list and bind native event 4568 callbackList = events[id][name]; 4569 if (!callbackList) { 4570 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4571 callbackList.fakeName = fakeName; 4572 callbackList.capture = capture; 4573 4574 // Add the nativeHandler to the callback list so that we can later unbind it 4575 callbackList.nativeHandler = nativeHandler; 4576 if (!w3cEventModel) { 4577 callbackList.proxyHandler = proxy(id); 4578 } 4579 4580 // Check if the target has native events support 4581 if (name === "ready") { 4582 bindOnReady(target, nativeHandler, self); 4583 } else { 4584 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4585 } 4586 } else { 4587 // If it already has an native handler then just push the callback 4588 callbackList.push({func: callback, scope: scope}); 4589 } 4590 } 4591 4592 target = callbackList = 0; // Clean memory for IE 4593 4594 return callback; 4595 }; 4596 4597 self.unbind = function(target, names, callback) { 4598 var id, callbackList, i, ci, name, eventMap; 4599 4600 // Don't bind to text nodes or comments 4601 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4602 return self; 4603 } 4604 4605 // Unbind event or events if the target has the expando 4606 id = target[expando]; 4607 if (id) { 4608 eventMap = events[id]; 4609 4610 // Specific callback 4611 if (names) { 4612 names = names.split(' '); 4613 i = names.length; 4614 while (i--) { 4615 name = names[i]; 4616 callbackList = eventMap[name]; 4617 4618 // Unbind the event if it exists in the map 4619 if (callbackList) { 4620 // Remove specified callback 4621 if (callback) { 4622 ci = callbackList.length; 4623 while (ci--) { 4624 if (callbackList[ci].func === callback) { 4625 callbackList.splice(ci, 1); 4626 } 4627 } 4628 } 4629 4630 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4631 if (!callback || callbackList.length === 0) { 4632 delete eventMap[name]; 4633 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4634 } 4635 } 4636 } 4637 } else { 4638 // All events for a specific element 4639 for (name in eventMap) { 4640 callbackList = eventMap[name]; 4641 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4642 } 4643 4644 eventMap = {}; 4645 } 4646 4647 // Check if object is empty, if it isn't then we won't remove the expando map 4648 for (name in eventMap) { 4649 return self; 4650 } 4651 4652 // Delete event object 4653 delete events[id]; 4654 4655 // Remove expando from target 4656 try { 4657 // IE will fail here since it can't delete properties from window 4658 delete target[expando]; 4659 } catch (ex) { 4660 // IE will set it to null 4661 target[expando] = null; 4662 } 4663 } 4664 4665 return self; 4666 }; 4667 4668 self.fire = function(target, name, args) { 4669 var id, event; 4670 4671 // Don't bind to text nodes or comments 4672 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4673 return self; 4674 } 4675 4676 // Build event object by patching the args 4677 event = fix(null, args); 4678 event.type = name; 4679 4680 do { 4681 // Found an expando that means there is listeners to execute 4682 id = target[expando]; 4683 if (id) { 4684 executeHandlers(event, id); 4685 } 4686 4687 // Walk up the DOM 4688 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4689 } while (target && !event.isPropagationStopped()); 4690 4691 return self; 4692 }; 4693 4694 self.clean = function(target) { 4695 var i, children, unbind = self.unbind; 4696 4697 // Don't bind to text nodes or comments 4698 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4699 return self; 4700 } 4701 4702 // Unbind any element on the specificed target 4703 if (target[expando]) { 4704 unbind(target); 4705 } 4706 4707 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 4708 if (!target.getElementsByTagName) { 4709 target = target.document; 4710 } 4711 4712 // Remove events from each child element 4713 if (target && target.getElementsByTagName) { 4714 unbind(target); 4715 4716 children = target.getElementsByTagName('*'); 4717 i = children.length; 4718 while (i--) { 4719 target = children[i]; 4720 4721 if (target[expando]) { 4722 unbind(target); 4723 } 4724 } 4725 } 4726 4727 return self; 4728 }; 4729 4730 self.callNativeHandler = function(id, evt) { 4731 if (events) { 4732 events[id][evt.type].nativeHandler(evt); 4733 } 4734 }; 4735 4736 self.destory = function() { 4737 events = {}; 4738 }; 4739 4740 // Legacy function calls 4741 4742 self.add = function(target, events, func, scope) { 4743 // Old API supported direct ID assignment 4744 if (typeof(target) === "string") { 4745 target = document.getElementById(target); 4746 } 4747 4748 // Old API supported multiple targets 4749 if (target && target instanceof Array) { 4750 var i = target.length; 4751 4752 while (i--) { 4753 self.add(target[i], events, func, scope); 4754 } 4755 4756 return; 4757 } 4758 4759 // Old API called ready init 4760 if (events === "init") { 4761 events = "ready"; 4762 } 4763 4764 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 4765 }; 4766 4767 self.remove = function(target, events, func, scope) { 4768 if (!target) { 4769 return self; 4770 } 4771 4772 // Old API supported direct ID assignment 4773 if (typeof(target) === "string") { 4774 target = document.getElementById(target); 4775 } 4776 4777 // Old API supported multiple targets 4778 if (target instanceof Array) { 4779 var i = target.length; 4780 4781 while (i--) { 4782 self.remove(target[i], events, func, scope); 4783 } 4784 4785 return self; 4786 } 4787 4788 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 4789 }; 4790 4791 self.clear = function(target) { 4792 // Old API supported direct ID assignment 4793 if (typeof(target) === "string") { 4794 target = document.getElementById(target); 4795 } 4796 4797 return self.clean(target); 4798 }; 4799 4800 self.cancel = function(e) { 4801 if (e) { 4802 self.prevent(e); 4803 self.stop(e); 4804 } 4805 4806 return false; 4807 }; 4808 4809 self.prevent = function(e) { 4810 if (!e.preventDefault) { 4811 e = fix(e); 4812 } 4813 4814 e.preventDefault(); 4815 4816 return false; 4817 }; 4818 4819 self.stop = function(e) { 4820 if (!e.stopPropagation) { 4821 e = fix(e); 4822 } 4823 4824 e.stopPropagation(); 4825 4826 return false; 4827 }; 4828 } 4829 4830 namespace.EventUtils = EventUtils; 4831 4832 namespace.Event = new EventUtils(function(id) { 4833 return function(evt) { 4834 tinymce.dom.Event.callNativeHandler(id, evt); 4835 }; 4836 }); 4837 4838 // Bind ready event when tinymce script is loaded 4839 namespace.Event.bind(window, 'ready', function() {}); 4840 4841 namespace = 0; 4842 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 4843 4844 tinymce.dom.TreeWalker = function(start_node, root_node) { 4845 var node = start_node; 4846 4847 function findSibling(node, start_name, sibling_name, shallow) { 4848 var sibling, parent; 4849 4850 if (node) { 4851 // Walk into nodes if it has a start 4852 if (!shallow && node[start_name]) 4853 return node[start_name]; 4854 4855 // Return the sibling if it has one 4856 if (node != root_node) { 4857 sibling = node[sibling_name]; 4858 if (sibling) 4859 return sibling; 4860 4861 // Walk up the parents to look for siblings 4862 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 4863 sibling = parent[sibling_name]; 4864 if (sibling) 4865 return sibling; 4866 } 4867 } 4868 } 4869 }; 4870 4871 this.current = function() { 4872 return node; 4873 }; 4874 4875 this.next = function(shallow) { 4876 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 4877 }; 4878 4879 this.prev = function(shallow) { 4880 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 4881 }; 4882 }; 4883 4884 (function(tinymce) { 4885 // Shorten names 4886 var each = tinymce.each, 4887 is = tinymce.is, 4888 isWebKit = tinymce.isWebKit, 4889 isIE = tinymce.isIE, 4890 Entities = tinymce.html.Entities, 4891 simpleSelectorRe = /^([a-z0-9],?)+$/i, 4892 whiteSpaceRegExp = /^[ \t\r\n]*$/; 4893 4894 tinymce.create('tinymce.dom.DOMUtils', { 4895 doc : null, 4896 root : null, 4897 files : null, 4898 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 4899 props : { 4900 "for" : "htmlFor", 4901 "class" : "className", 4902 className : "className", 4903 checked : "checked", 4904 disabled : "disabled", 4905 maxlength : "maxLength", 4906 readonly : "readOnly", 4907 selected : "selected", 4908 value : "value", 4909 id : "id", 4910 name : "name", 4911 type : "type" 4912 }, 4913 4914 DOMUtils : function(d, s) { 4915 var t = this, globalStyle, name, blockElementsMap; 4916 4917 t.doc = d; 4918 t.win = window; 4919 t.files = {}; 4920 t.cssFlicker = false; 4921 t.counter = 0; 4922 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 4923 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 4924 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 4925 4926 t.settings = s = tinymce.extend({ 4927 keep_values : false, 4928 hex_colors : 1 4929 }, s); 4930 4931 t.schema = s.schema; 4932 t.styles = new tinymce.html.Styles({ 4933 url_converter : s.url_converter, 4934 url_converter_scope : s.url_converter_scope 4935 }, s.schema); 4936 4937 // Fix IE6SP2 flicker and check it failed for pre SP2 4938 if (tinymce.isIE6) { 4939 try { 4940 d.execCommand('BackgroundImageCache', false, true); 4941 } catch (e) { 4942 t.cssFlicker = true; 4943 } 4944 } 4945 4946 t.fixDoc(d); 4947 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 4948 tinymce.addUnload(t.destroy, t); 4949 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 4950 4951 t.isBlock = function(node) { 4952 // This function is called in module pattern style since it might be executed with the wrong this scope 4953 var type = node.nodeType; 4954 4955 // If it's a node then check the type and use the nodeName 4956 if (type) 4957 return !!(type === 1 && blockElementsMap[node.nodeName]); 4958 4959 return !!blockElementsMap[node]; 4960 }; 4961 }, 4962 4963 fixDoc: function(doc) { 4964 var settings = this.settings, name; 4965 4966 if (isIE && settings.schema) { 4967 // Add missing HTML 4/5 elements to IE 4968 ('abbr article aside audio canvas ' + 4969 'details figcaption figure footer ' + 4970 'header hgroup mark menu meter nav ' + 4971 'output progress section summary ' + 4972 'time video').replace(/\w+/g, function(name) { 4973 doc.createElement(name); 4974 }); 4975 4976 // Create all custom elements 4977 for (name in settings.schema.getCustomElements()) { 4978 doc.createElement(name); 4979 } 4980 } 4981 }, 4982 4983 clone: function(node, deep) { 4984 var self = this, clone, doc; 4985 4986 // TODO: Add feature detection here in the future 4987 if (!isIE || node.nodeType !== 1 || deep) { 4988 return node.cloneNode(deep); 4989 } 4990 4991 doc = self.doc; 4992 4993 // Make a HTML5 safe shallow copy 4994 if (!deep) { 4995 clone = doc.createElement(node.nodeName); 4996 4997 // Copy attribs 4998 each(self.getAttribs(node), function(attr) { 4999 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5000 }); 5001 5002 return clone; 5003 } 5004 /* 5005 // Setup HTML5 patched document fragment 5006 if (!self.frag) { 5007 self.frag = doc.createDocumentFragment(); 5008 self.fixDoc(self.frag); 5009 } 5010 5011 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5012 clone = doc.createElement('div'); 5013 self.frag.appendChild(clone); 5014 clone.innerHTML = node.outerHTML; 5015 self.frag.removeChild(clone); 5016 */ 5017 return clone.firstChild; 5018 }, 5019 5020 getRoot : function() { 5021 var t = this, s = t.settings; 5022 5023 return (s && t.get(s.root_element)) || t.doc.body; 5024 }, 5025 5026 getViewPort : function(w) { 5027 var d, b; 5028 5029 w = !w ? this.win : w; 5030 d = w.document; 5031 b = this.boxModel ? d.documentElement : d.body; 5032 5033 // Returns viewport size excluding scrollbars 5034 return { 5035 x : w.pageXOffset || b.scrollLeft, 5036 y : w.pageYOffset || b.scrollTop, 5037 w : w.innerWidth || b.clientWidth, 5038 h : w.innerHeight || b.clientHeight 5039 }; 5040 }, 5041 5042 getRect : function(e) { 5043 var p, t = this, sr; 5044 5045 e = t.get(e); 5046 p = t.getPos(e); 5047 sr = t.getSize(e); 5048 5049 return { 5050 x : p.x, 5051 y : p.y, 5052 w : sr.w, 5053 h : sr.h 5054 }; 5055 }, 5056 5057 getSize : function(e) { 5058 var t = this, w, h; 5059 5060 e = t.get(e); 5061 w = t.getStyle(e, 'width'); 5062 h = t.getStyle(e, 'height'); 5063 5064 // Non pixel value, then force offset/clientWidth 5065 if (w.indexOf('px') === -1) 5066 w = 0; 5067 5068 // Non pixel value, then force offset/clientWidth 5069 if (h.indexOf('px') === -1) 5070 h = 0; 5071 5072 return { 5073 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5074 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5075 }; 5076 }, 5077 5078 getParent : function(n, f, r) { 5079 return this.getParents(n, f, r, false); 5080 }, 5081 5082 getParents : function(n, f, r, c) { 5083 var t = this, na, se = t.settings, o = []; 5084 5085 n = t.get(n); 5086 c = c === undefined; 5087 5088 if (se.strict_root) 5089 r = r || t.getRoot(); 5090 5091 // Wrap node name as func 5092 if (is(f, 'string')) { 5093 na = f; 5094 5095 if (f === '*') { 5096 f = function(n) {return n.nodeType == 1;}; 5097 } else { 5098 f = function(n) { 5099 return t.is(n, na); 5100 }; 5101 } 5102 } 5103 5104 while (n) { 5105 if (n == r || !n.nodeType || n.nodeType === 9) 5106 break; 5107 5108 if (!f || f(n)) { 5109 if (c) 5110 o.push(n); 5111 else 5112 return n; 5113 } 5114 5115 n = n.parentNode; 5116 } 5117 5118 return c ? o : null; 5119 }, 5120 5121 get : function(e) { 5122 var n; 5123 5124 if (e && this.doc && typeof(e) == 'string') { 5125 n = e; 5126 e = this.doc.getElementById(e); 5127 5128 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5129 if (e && e.id !== n) 5130 return this.doc.getElementsByName(n)[1]; 5131 } 5132 5133 return e; 5134 }, 5135 5136 getNext : function(node, selector) { 5137 return this._findSib(node, selector, 'nextSibling'); 5138 }, 5139 5140 getPrev : function(node, selector) { 5141 return this._findSib(node, selector, 'previousSibling'); 5142 }, 5143 5144 5145 select : function(pa, s) { 5146 var t = this; 5147 5148 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5149 }, 5150 5151 is : function(n, selector) { 5152 var i; 5153 5154 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5155 if (n.length === undefined) { 5156 // Simple all selector 5157 if (selector === '*') 5158 return n.nodeType == 1; 5159 5160 // Simple selector just elements 5161 if (simpleSelectorRe.test(selector)) { 5162 selector = selector.toLowerCase().split(/,/); 5163 n = n.nodeName.toLowerCase(); 5164 5165 for (i = selector.length - 1; i >= 0; i--) { 5166 if (selector[i] == n) 5167 return true; 5168 } 5169 5170 return false; 5171 } 5172 } 5173 5174 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5175 }, 5176 5177 5178 add : function(p, n, a, h, c) { 5179 var t = this; 5180 5181 return this.run(p, function(p) { 5182 var e, k; 5183 5184 e = is(n, 'string') ? t.doc.createElement(n) : n; 5185 t.setAttribs(e, a); 5186 5187 if (h) { 5188 if (h.nodeType) 5189 e.appendChild(h); 5190 else 5191 t.setHTML(e, h); 5192 } 5193 5194 return !c ? p.appendChild(e) : e; 5195 }); 5196 }, 5197 5198 create : function(n, a, h) { 5199 return this.add(this.doc.createElement(n), n, a, h, 1); 5200 }, 5201 5202 createHTML : function(n, a, h) { 5203 var o = '', t = this, k; 5204 5205 o += '<' + n; 5206 5207 for (k in a) { 5208 if (a.hasOwnProperty(k)) 5209 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5210 } 5211 5212 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5213 if (typeof(h) != "undefined") 5214 return o + '>' + h + '</' + n + '>'; 5215 5216 return o + ' />'; 5217 }, 5218 5219 remove : function(node, keep_children) { 5220 return this.run(node, function(node) { 5221 var child, parent = node.parentNode; 5222 5223 if (!parent) 5224 return null; 5225 5226 if (keep_children) { 5227 while (child = node.firstChild) { 5228 // IE 8 will crash if you don't remove completely empty text nodes 5229 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5230 parent.insertBefore(child, node); 5231 else 5232 node.removeChild(child); 5233 } 5234 } 5235 5236 return parent.removeChild(node); 5237 }); 5238 }, 5239 5240 setStyle : function(n, na, v) { 5241 var t = this; 5242 5243 return t.run(n, function(e) { 5244 var s, i; 5245 5246 s = e.style; 5247 5248 // Camelcase it, if needed 5249 na = na.replace(/-(\D)/g, function(a, b){ 5250 return b.toUpperCase(); 5251 }); 5252 5253 // Default px suffix on these 5254 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5255 v += 'px'; 5256 5257 switch (na) { 5258 case 'opacity': 5259 // IE specific opacity 5260 if (isIE) { 5261 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5262 5263 if (!n.currentStyle || !n.currentStyle.hasLayout) 5264 s.display = 'inline-block'; 5265 } 5266 5267 // Fix for older browsers 5268 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5269 break; 5270 5271 case 'float': 5272 isIE ? s.styleFloat = v : s.cssFloat = v; 5273 break; 5274 5275 default: 5276 s[na] = v || ''; 5277 } 5278 5279 // Force update of the style data 5280 if (t.settings.update_styles) 5281 t.setAttrib(e, 'data-mce-style'); 5282 }); 5283 }, 5284 5285 getStyle : function(n, na, c) { 5286 n = this.get(n); 5287 5288 if (!n) 5289 return; 5290 5291 // Gecko 5292 if (this.doc.defaultView && c) { 5293 // Remove camelcase 5294 na = na.replace(/[A-Z]/g, function(a){ 5295 return '-' + a; 5296 }); 5297 5298 try { 5299 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5300 } catch (ex) { 5301 // Old safari might fail 5302 return null; 5303 } 5304 } 5305 5306 // Camelcase it, if needed 5307 na = na.replace(/-(\D)/g, function(a, b){ 5308 return b.toUpperCase(); 5309 }); 5310 5311 if (na == 'float') 5312 na = isIE ? 'styleFloat' : 'cssFloat'; 5313 5314 // IE & Opera 5315 if (n.currentStyle && c) 5316 return n.currentStyle[na]; 5317 5318 return n.style ? n.style[na] : undefined; 5319 }, 5320 5321 setStyles : function(e, o) { 5322 var t = this, s = t.settings, ol; 5323 5324 ol = s.update_styles; 5325 s.update_styles = 0; 5326 5327 each(o, function(v, n) { 5328 t.setStyle(e, n, v); 5329 }); 5330 5331 // Update style info 5332 s.update_styles = ol; 5333 if (s.update_styles) 5334 t.setAttrib(e, s.cssText); 5335 }, 5336 5337 removeAllAttribs: function(e) { 5338 return this.run(e, function(e) { 5339 var i, attrs = e.attributes; 5340 for (i = attrs.length - 1; i >= 0; i--) { 5341 e.removeAttributeNode(attrs.item(i)); 5342 } 5343 }); 5344 }, 5345 5346 setAttrib : function(e, n, v) { 5347 var t = this; 5348 5349 // Whats the point 5350 if (!e || !n) 5351 return; 5352 5353 // Strict XML mode 5354 if (t.settings.strict) 5355 n = n.toLowerCase(); 5356 5357 return this.run(e, function(e) { 5358 var s = t.settings; 5359 var originalValue = e.getAttribute(n); 5360 if (v !== null) { 5361 switch (n) { 5362 case "style": 5363 if (!is(v, 'string')) { 5364 each(v, function(v, n) { 5365 t.setStyle(e, n, v); 5366 }); 5367 5368 return; 5369 } 5370 5371 // No mce_style for elements with these since they might get resized by the user 5372 if (s.keep_values) { 5373 if (v && !t._isRes(v)) 5374 e.setAttribute('data-mce-style', v, 2); 5375 else 5376 e.removeAttribute('data-mce-style', 2); 5377 } 5378 5379 e.style.cssText = v; 5380 break; 5381 5382 case "class": 5383 e.className = v || ''; // Fix IE null bug 5384 break; 5385 5386 case "src": 5387 case "href": 5388 if (s.keep_values) { 5389 if (s.url_converter) 5390 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5391 5392 t.setAttrib(e, 'data-mce-' + n, v, 2); 5393 } 5394 5395 break; 5396 5397 case "shape": 5398 e.setAttribute('data-mce-style', v); 5399 break; 5400 } 5401 } 5402 if (is(v) && v !== null && v.length !== 0) 5403 e.setAttribute(n, '' + v, 2); 5404 else 5405 e.removeAttribute(n, 2); 5406 5407 // fire onChangeAttrib event for attributes that have changed 5408 if (tinyMCE.activeEditor && originalValue != v) { 5409 var ed = tinyMCE.activeEditor; 5410 ed.onSetAttrib.dispatch(ed, e, n, v); 5411 } 5412 }); 5413 }, 5414 5415 setAttribs : function(e, o) { 5416 var t = this; 5417 5418 return this.run(e, function(e) { 5419 each(o, function(v, n) { 5420 t.setAttrib(e, n, v); 5421 }); 5422 }); 5423 }, 5424 5425 getAttrib : function(e, n, dv) { 5426 var v, t = this, undef; 5427 5428 e = t.get(e); 5429 5430 if (!e || e.nodeType !== 1) 5431 return dv === undef ? false : dv; 5432 5433 if (!is(dv)) 5434 dv = ''; 5435 5436 // Try the mce variant for these 5437 if (/^(src|href|style|coords|shape)$/.test(n)) { 5438 v = e.getAttribute("data-mce-" + n); 5439 5440 if (v) 5441 return v; 5442 } 5443 5444 if (isIE && t.props[n]) { 5445 v = e[t.props[n]]; 5446 v = v && v.nodeValue ? v.nodeValue : v; 5447 } 5448 5449 if (!v) 5450 v = e.getAttribute(n, 2); 5451 5452 // Check boolean attribs 5453 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5454 if (e[t.props[n]] === true && v === '') 5455 return n; 5456 5457 return v ? n : ''; 5458 } 5459 5460 // Inner input elements will override attributes on form elements 5461 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5462 return e.getAttributeNode(n).nodeValue; 5463 5464 if (n === 'style') { 5465 v = v || e.style.cssText; 5466 5467 if (v) { 5468 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5469 5470 if (t.settings.keep_values && !t._isRes(v)) 5471 e.setAttribute('data-mce-style', v); 5472 } 5473 } 5474 5475 // Remove Apple and WebKit stuff 5476 if (isWebKit && n === "class" && v) 5477 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5478 5479 // Handle IE issues 5480 if (isIE) { 5481 switch (n) { 5482 case 'rowspan': 5483 case 'colspan': 5484 // IE returns 1 as default value 5485 if (v === 1) 5486 v = ''; 5487 5488 break; 5489 5490 case 'size': 5491 // IE returns +0 as default value for size 5492 if (v === '+0' || v === 20 || v === 0) 5493 v = ''; 5494 5495 break; 5496 5497 case 'width': 5498 case 'height': 5499 case 'vspace': 5500 case 'checked': 5501 case 'disabled': 5502 case 'readonly': 5503 if (v === 0) 5504 v = ''; 5505 5506 break; 5507 5508 case 'hspace': 5509 // IE returns -1 as default value 5510 if (v === -1) 5511 v = ''; 5512 5513 break; 5514 5515 case 'maxlength': 5516 case 'tabindex': 5517 // IE returns default value 5518 if (v === 32768 || v === 2147483647 || v === '32768') 5519 v = ''; 5520 5521 break; 5522 5523 case 'multiple': 5524 case 'compact': 5525 case 'noshade': 5526 case 'nowrap': 5527 if (v === 65535) 5528 return n; 5529 5530 return dv; 5531 5532 case 'shape': 5533 v = v.toLowerCase(); 5534 break; 5535 5536 default: 5537 // IE has odd anonymous function for event attributes 5538 if (n.indexOf('on') === 0 && v) 5539 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5540 } 5541 } 5542 5543 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5544 }, 5545 5546 getPos : function(n, ro) { 5547 var t = this, x = 0, y = 0, e, d = t.doc, r; 5548 5549 n = t.get(n); 5550 ro = ro || d.body; 5551 5552 if (n) { 5553 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5554 if (n.getBoundingClientRect) { 5555 n = n.getBoundingClientRect(); 5556 e = t.boxModel ? d.documentElement : d.body; 5557 5558 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5559 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5560 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5561 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5562 5563 return {x : x, y : y}; 5564 } 5565 5566 r = n; 5567 while (r && r != ro && r.nodeType) { 5568 x += r.offsetLeft || 0; 5569 y += r.offsetTop || 0; 5570 r = r.offsetParent; 5571 } 5572 5573 r = n.parentNode; 5574 while (r && r != ro && r.nodeType) { 5575 x -= r.scrollLeft || 0; 5576 y -= r.scrollTop || 0; 5577 r = r.parentNode; 5578 } 5579 } 5580 5581 return {x : x, y : y}; 5582 }, 5583 5584 parseStyle : function(st) { 5585 return this.styles.parse(st); 5586 }, 5587 5588 serializeStyle : function(o, name) { 5589 return this.styles.serialize(o, name); 5590 }, 5591 5592 addStyle: function(cssText) { 5593 var doc = this.doc, head; 5594 5595 // Create style element if needed 5596 styleElm = doc.getElementById('mceDefaultStyles'); 5597 if (!styleElm) { 5598 styleElm = doc.createElement('style'), 5599 styleElm.id = 'mceDefaultStyles'; 5600 styleElm.type = 'text/css'; 5601 5602 head = doc.getElementsByTagName('head')[0] 5603 if (head.firstChild) { 5604 head.insertBefore(styleElm, head.firstChild); 5605 } else { 5606 head.appendChild(styleElm); 5607 } 5608 } 5609 5610 // Append style data to old or new style element 5611 if (styleElm.styleSheet) { 5612 styleElm.styleSheet.cssText += cssText; 5613 } else { 5614 styleElm.appendChild(doc.createTextNode(cssText)); 5615 } 5616 }, 5617 5618 loadCSS : function(u) { 5619 var t = this, d = t.doc, head; 5620 5621 if (!u) 5622 u = ''; 5623 5624 head = d.getElementsByTagName('head')[0]; 5625 5626 each(u.split(','), function(u) { 5627 var link; 5628 5629 if (t.files[u]) 5630 return; 5631 5632 t.files[u] = true; 5633 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5634 5635 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5636 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5637 // It's ugly but it seems to work fine. 5638 if (isIE && d.documentMode && d.recalc) { 5639 link.onload = function() { 5640 if (d.recalc) 5641 d.recalc(); 5642 5643 link.onload = null; 5644 }; 5645 } 5646 5647 head.appendChild(link); 5648 }); 5649 }, 5650 5651 addClass : function(e, c) { 5652 return this.run(e, function(e) { 5653 var o; 5654 5655 if (!c) 5656 return 0; 5657 5658 if (this.hasClass(e, c)) 5659 return e.className; 5660 5661 o = this.removeClass(e, c); 5662 5663 return e.className = (o != '' ? (o + ' ') : '') + c; 5664 }); 5665 }, 5666 5667 removeClass : function(e, c) { 5668 var t = this, re; 5669 5670 return t.run(e, function(e) { 5671 var v; 5672 5673 if (t.hasClass(e, c)) { 5674 if (!re) 5675 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5676 5677 v = e.className.replace(re, ' '); 5678 v = tinymce.trim(v != ' ' ? v : ''); 5679 5680 e.className = v; 5681 5682 // Empty class attr 5683 if (!v) { 5684 e.removeAttribute('class'); 5685 e.removeAttribute('className'); 5686 } 5687 5688 return v; 5689 } 5690 5691 return e.className; 5692 }); 5693 }, 5694 5695 hasClass : function(n, c) { 5696 n = this.get(n); 5697 5698 if (!n || !c) 5699 return false; 5700 5701 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5702 }, 5703 5704 show : function(e) { 5705 return this.setStyle(e, 'display', 'block'); 5706 }, 5707 5708 hide : function(e) { 5709 return this.setStyle(e, 'display', 'none'); 5710 }, 5711 5712 isHidden : function(e) { 5713 e = this.get(e); 5714 5715 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 5716 }, 5717 5718 uniqueId : function(p) { 5719 return (!p ? 'mce_' : p) + (this.counter++); 5720 }, 5721 5722 setHTML : function(element, html) { 5723 var self = this; 5724 5725 return self.run(element, function(element) { 5726 if (isIE) { 5727 // Remove all child nodes, IE keeps empty text nodes in DOM 5728 while (element.firstChild) 5729 element.removeChild(element.firstChild); 5730 5731 try { 5732 // IE will remove comments from the beginning 5733 // unless you padd the contents with something 5734 element.innerHTML = '<br />' + html; 5735 element.removeChild(element.firstChild); 5736 } catch (ex) { 5737 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p 5738 // This seems to fix this problem 5739 5740 // Create new div with HTML contents and a BR infront to keep comments 5741 var newElement = self.create('div'); 5742 newElement.innerHTML = '<br />' + html; 5743 5744 // Add all children from div to target 5745 each (tinymce.grep(newElement.childNodes), function(node, i) { 5746 // Skip br element 5747 if (i && element.canHaveHTML) 5748 element.appendChild(node); 5749 }); 5750 } 5751 } else 5752 element.innerHTML = html; 5753 5754 return html; 5755 }); 5756 }, 5757 5758 getOuterHTML : function(elm) { 5759 var doc, self = this; 5760 5761 elm = self.get(elm); 5762 5763 if (!elm) 5764 return null; 5765 5766 if (elm.nodeType === 1 && self.hasOuterHTML) 5767 return elm.outerHTML; 5768 5769 doc = (elm.ownerDocument || self.doc).createElement("body"); 5770 doc.appendChild(elm.cloneNode(true)); 5771 5772 return doc.innerHTML; 5773 }, 5774 5775 setOuterHTML : function(e, h, d) { 5776 var t = this; 5777 5778 function setHTML(e, h, d) { 5779 var n, tp; 5780 5781 tp = d.createElement("body"); 5782 tp.innerHTML = h; 5783 5784 n = tp.lastChild; 5785 while (n) { 5786 t.insertAfter(n.cloneNode(true), e); 5787 n = n.previousSibling; 5788 } 5789 5790 t.remove(e); 5791 }; 5792 5793 return this.run(e, function(e) { 5794 e = t.get(e); 5795 5796 // Only set HTML on elements 5797 if (e.nodeType == 1) { 5798 d = d || e.ownerDocument || t.doc; 5799 5800 if (isIE) { 5801 try { 5802 // Try outerHTML for IE it sometimes produces an unknown runtime error 5803 if (isIE && e.nodeType == 1) 5804 e.outerHTML = h; 5805 else 5806 setHTML(e, h, d); 5807 } catch (ex) { 5808 // Fix for unknown runtime error 5809 setHTML(e, h, d); 5810 } 5811 } else 5812 setHTML(e, h, d); 5813 } 5814 }); 5815 }, 5816 5817 decode : Entities.decode, 5818 5819 encode : Entities.encodeAllRaw, 5820 5821 insertAfter : function(node, reference_node) { 5822 reference_node = this.get(reference_node); 5823 5824 return this.run(node, function(node) { 5825 var parent, nextSibling; 5826 5827 parent = reference_node.parentNode; 5828 nextSibling = reference_node.nextSibling; 5829 5830 if (nextSibling) 5831 parent.insertBefore(node, nextSibling); 5832 else 5833 parent.appendChild(node); 5834 5835 return node; 5836 }); 5837 }, 5838 5839 replace : function(n, o, k) { 5840 var t = this; 5841 5842 if (is(o, 'array')) 5843 n = n.cloneNode(true); 5844 5845 return t.run(o, function(o) { 5846 if (k) { 5847 each(tinymce.grep(o.childNodes), function(c) { 5848 n.appendChild(c); 5849 }); 5850 } 5851 5852 return o.parentNode.replaceChild(n, o); 5853 }); 5854 }, 5855 5856 rename : function(elm, name) { 5857 var t = this, newElm; 5858 5859 if (elm.nodeName != name.toUpperCase()) { 5860 // Rename block element 5861 newElm = t.create(name); 5862 5863 // Copy attribs to new block 5864 each(t.getAttribs(elm), function(attr_node) { 5865 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 5866 }); 5867 5868 // Replace block 5869 t.replace(newElm, elm, 1); 5870 } 5871 5872 return newElm || elm; 5873 }, 5874 5875 findCommonAncestor : function(a, b) { 5876 var ps = a, pe; 5877 5878 while (ps) { 5879 pe = b; 5880 5881 while (pe && ps != pe) 5882 pe = pe.parentNode; 5883 5884 if (ps == pe) 5885 break; 5886 5887 ps = ps.parentNode; 5888 } 5889 5890 if (!ps && a.ownerDocument) 5891 return a.ownerDocument.documentElement; 5892 5893 return ps; 5894 }, 5895 5896 toHex : function(s) { 5897 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 5898 5899 function hex(s) { 5900 s = parseInt(s, 10).toString(16); 5901 5902 return s.length > 1 ? s : '0' + s; // 0 -> 00 5903 }; 5904 5905 if (c) { 5906 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 5907 5908 return s; 5909 } 5910 5911 return s; 5912 }, 5913 5914 getClasses : function() { 5915 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 5916 5917 if (t.classes) 5918 return t.classes; 5919 5920 function addClasses(s) { 5921 // IE style imports 5922 each(s.imports, function(r) { 5923 addClasses(r); 5924 }); 5925 5926 each(s.cssRules || s.rules, function(r) { 5927 // Real type or fake it on IE 5928 switch (r.type || 1) { 5929 // Rule 5930 case 1: 5931 if (r.selectorText) { 5932 each(r.selectorText.split(','), function(v) { 5933 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 5934 5935 // Is internal or it doesn't contain a class 5936 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 5937 return; 5938 5939 // Remove everything but class name 5940 ov = v; 5941 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 5942 5943 // Filter classes 5944 if (f && !(v = f(v, ov))) 5945 return; 5946 5947 if (!lo[v]) { 5948 cl.push({'class' : v}); 5949 lo[v] = 1; 5950 } 5951 }); 5952 } 5953 break; 5954 5955 // Import 5956 case 3: 5957 addClasses(r.styleSheet); 5958 break; 5959 } 5960 }); 5961 }; 5962 5963 try { 5964 each(t.doc.styleSheets, addClasses); 5965 } catch (ex) { 5966 // Ignore 5967 } 5968 5969 if (cl.length > 0) 5970 t.classes = cl; 5971 5972 return cl; 5973 }, 5974 5975 run : function(e, f, s) { 5976 var t = this, o; 5977 5978 if (t.doc && typeof(e) === 'string') 5979 e = t.get(e); 5980 5981 if (!e) 5982 return false; 5983 5984 s = s || this; 5985 if (!e.nodeType && (e.length || e.length === 0)) { 5986 o = []; 5987 5988 each(e, function(e, i) { 5989 if (e) { 5990 if (typeof(e) == 'string') 5991 e = t.doc.getElementById(e); 5992 5993 o.push(f.call(s, e, i)); 5994 } 5995 }); 5996 5997 return o; 5998 } 5999 6000 return f.call(s, e); 6001 }, 6002 6003 getAttribs : function(n) { 6004 var o; 6005 6006 n = this.get(n); 6007 6008 if (!n) 6009 return []; 6010 6011 if (isIE) { 6012 o = []; 6013 6014 // Object will throw exception in IE 6015 if (n.nodeName == 'OBJECT') 6016 return n.attributes; 6017 6018 // IE doesn't keep the selected attribute if you clone option elements 6019 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6020 o.push({specified : 1, nodeName : 'selected'}); 6021 6022 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6023 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6024 o.push({specified : 1, nodeName : a}); 6025 }); 6026 6027 return o; 6028 } 6029 6030 return n.attributes; 6031 }, 6032 6033 isEmpty : function(node, elements) { 6034 var self = this, i, attributes, type, walker, name, brCount = 0; 6035 6036 node = node.firstChild; 6037 if (node) { 6038 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6039 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6040 6041 do { 6042 type = node.nodeType; 6043 6044 if (type === 1) { 6045 // Ignore bogus elements 6046 if (node.getAttribute('data-mce-bogus')) 6047 continue; 6048 6049 // Keep empty elements like <img /> 6050 name = node.nodeName.toLowerCase(); 6051 if (elements && elements[name]) { 6052 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6053 if (name === 'br') { 6054 brCount++; 6055 continue; 6056 } 6057 6058 return false; 6059 } 6060 6061 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6062 attributes = self.getAttribs(node); 6063 i = node.attributes.length; 6064 while (i--) { 6065 name = node.attributes[i].nodeName; 6066 if (name === "name" || name === 'data-mce-bookmark') 6067 return false; 6068 } 6069 } 6070 6071 // Keep comment nodes 6072 if (type == 8) 6073 return false; 6074 6075 // Keep non whitespace text nodes 6076 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6077 return false; 6078 } while (node = walker.next()); 6079 } 6080 6081 return brCount <= 1; 6082 }, 6083 6084 destroy : function(s) { 6085 var t = this; 6086 6087 t.win = t.doc = t.root = t.events = t.frag = null; 6088 6089 // Manual destroy then remove unload handler 6090 if (!s) 6091 tinymce.removeUnload(t.destroy); 6092 }, 6093 6094 createRng : function() { 6095 var d = this.doc; 6096 6097 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6098 }, 6099 6100 nodeIndex : function(node, normalized) { 6101 var idx = 0, lastNodeType, lastNode, nodeType; 6102 6103 if (node) { 6104 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6105 nodeType = node.nodeType; 6106 6107 // Normalize text nodes 6108 if (normalized && nodeType == 3) { 6109 if (nodeType == lastNodeType || !node.nodeValue.length) 6110 continue; 6111 } 6112 idx++; 6113 lastNodeType = nodeType; 6114 } 6115 } 6116 6117 return idx; 6118 }, 6119 6120 split : function(pe, e, re) { 6121 var t = this, r = t.createRng(), bef, aft, pa; 6122 6123 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6124 // but we don't want that in our code since it serves no purpose for the end user 6125 // For example if this is chopped: 6126 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6127 // would produce: 6128 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6129 // this function will then trim of empty edges and produce: 6130 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6131 function trim(node) { 6132 var i, children = node.childNodes, type = node.nodeType; 6133 6134 function surroundedBySpans(node) { 6135 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6136 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6137 return previousIsSpan && nextIsSpan; 6138 } 6139 6140 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6141 return; 6142 6143 for (i = children.length - 1; i >= 0; i--) 6144 trim(children[i]); 6145 6146 if (type != 9) { 6147 // Keep non whitespace text nodes 6148 if (type == 3 && node.nodeValue.length > 0) { 6149 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6150 // Also keep text nodes with only spaces if surrounded by spans. 6151 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6152 var trimmedLength = tinymce.trim(node.nodeValue).length; 6153 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6154 return; 6155 } else if (type == 1) { 6156 // If the only child is a bookmark then move it up 6157 children = node.childNodes; 6158 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6159 node.parentNode.insertBefore(children[0], node); 6160 6161 // Keep non empty elements or img, hr etc 6162 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6163 return; 6164 } 6165 6166 t.remove(node); 6167 } 6168 6169 return node; 6170 }; 6171 6172 if (pe && e) { 6173 // Get before chunk 6174 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6175 r.setEnd(e.parentNode, t.nodeIndex(e)); 6176 bef = r.extractContents(); 6177 6178 // Get after chunk 6179 r = t.createRng(); 6180 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6181 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6182 aft = r.extractContents(); 6183 6184 // Insert before chunk 6185 pa = pe.parentNode; 6186 pa.insertBefore(trim(bef), pe); 6187 6188 // Insert middle chunk 6189 if (re) 6190 pa.replaceChild(re, e); 6191 else 6192 pa.insertBefore(e, pe); 6193 6194 // Insert after chunk 6195 pa.insertBefore(trim(aft), pe); 6196 t.remove(pe); 6197 6198 return re || e; 6199 } 6200 }, 6201 6202 bind : function(target, name, func, scope) { 6203 return this.events.add(target, name, func, scope || this); 6204 }, 6205 6206 unbind : function(target, name, func) { 6207 return this.events.remove(target, name, func); 6208 }, 6209 6210 fire : function(target, name, evt) { 6211 return this.events.fire(target, name, evt); 6212 }, 6213 6214 // Returns the content editable state of a node 6215 getContentEditable: function(node) { 6216 var contentEditable; 6217 6218 // Check type 6219 if (node.nodeType != 1) { 6220 return null; 6221 } 6222 6223 // Check for fake content editable 6224 contentEditable = node.getAttribute("data-mce-contenteditable"); 6225 if (contentEditable && contentEditable !== "inherit") { 6226 return contentEditable; 6227 } 6228 6229 // Check for real content editable 6230 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6231 }, 6232 6233 6234 _findSib : function(node, selector, name) { 6235 var t = this, f = selector; 6236 6237 if (node) { 6238 // If expression make a function of it using is 6239 if (is(f, 'string')) { 6240 f = function(node) { 6241 return t.is(node, selector); 6242 }; 6243 } 6244 6245 // Loop all siblings 6246 for (node = node[name]; node; node = node[name]) { 6247 if (f(node)) 6248 return node; 6249 } 6250 } 6251 6252 return null; 6253 }, 6254 6255 _isRes : function(c) { 6256 // Is live resizble element 6257 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6258 } 6259 6260 /* 6261 walk : function(n, f, s) { 6262 var d = this.doc, w; 6263 6264 if (d.createTreeWalker) { 6265 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6266 6267 while ((n = w.nextNode()) != null) 6268 f.call(s || this, n); 6269 } else 6270 tinymce.walk(n, f, 'childNodes', s); 6271 } 6272 */ 6273 6274 /* 6275 toRGB : function(s) { 6276 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6277 6278 if (c) { 6279 // #FFF -> #FFFFFF 6280 if (!is(c[3])) 6281 c[3] = c[2] = c[1]; 6282 6283 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6284 } 6285 6286 return s; 6287 } 6288 */ 6289 }); 6290 6291 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6292 })(tinymce); 6293 6294 (function(ns) { 6295 // Range constructor 6296 function Range(dom) { 6297 var t = this, 6298 doc = dom.doc, 6299 EXTRACT = 0, 6300 CLONE = 1, 6301 DELETE = 2, 6302 TRUE = true, 6303 FALSE = false, 6304 START_OFFSET = 'startOffset', 6305 START_CONTAINER = 'startContainer', 6306 END_CONTAINER = 'endContainer', 6307 END_OFFSET = 'endOffset', 6308 extend = tinymce.extend, 6309 nodeIndex = dom.nodeIndex; 6310 6311 extend(t, { 6312 // Inital states 6313 startContainer : doc, 6314 startOffset : 0, 6315 endContainer : doc, 6316 endOffset : 0, 6317 collapsed : TRUE, 6318 commonAncestorContainer : doc, 6319 6320 // Range constants 6321 START_TO_START : 0, 6322 START_TO_END : 1, 6323 END_TO_END : 2, 6324 END_TO_START : 3, 6325 6326 // Public methods 6327 setStart : setStart, 6328 setEnd : setEnd, 6329 setStartBefore : setStartBefore, 6330 setStartAfter : setStartAfter, 6331 setEndBefore : setEndBefore, 6332 setEndAfter : setEndAfter, 6333 collapse : collapse, 6334 selectNode : selectNode, 6335 selectNodeContents : selectNodeContents, 6336 compareBoundaryPoints : compareBoundaryPoints, 6337 deleteContents : deleteContents, 6338 extractContents : extractContents, 6339 cloneContents : cloneContents, 6340 insertNode : insertNode, 6341 surroundContents : surroundContents, 6342 cloneRange : cloneRange, 6343 toStringIE : toStringIE 6344 }); 6345 6346 function createDocumentFragment() { 6347 return doc.createDocumentFragment(); 6348 }; 6349 6350 function setStart(n, o) { 6351 _setEndPoint(TRUE, n, o); 6352 }; 6353 6354 function setEnd(n, o) { 6355 _setEndPoint(FALSE, n, o); 6356 }; 6357 6358 function setStartBefore(n) { 6359 setStart(n.parentNode, nodeIndex(n)); 6360 }; 6361 6362 function setStartAfter(n) { 6363 setStart(n.parentNode, nodeIndex(n) + 1); 6364 }; 6365 6366 function setEndBefore(n) { 6367 setEnd(n.parentNode, nodeIndex(n)); 6368 }; 6369 6370 function setEndAfter(n) { 6371 setEnd(n.parentNode, nodeIndex(n) + 1); 6372 }; 6373 6374 function collapse(ts) { 6375 if (ts) { 6376 t[END_CONTAINER] = t[START_CONTAINER]; 6377 t[END_OFFSET] = t[START_OFFSET]; 6378 } else { 6379 t[START_CONTAINER] = t[END_CONTAINER]; 6380 t[START_OFFSET] = t[END_OFFSET]; 6381 } 6382 6383 t.collapsed = TRUE; 6384 }; 6385 6386 function selectNode(n) { 6387 setStartBefore(n); 6388 setEndAfter(n); 6389 }; 6390 6391 function selectNodeContents(n) { 6392 setStart(n, 0); 6393 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6394 }; 6395 6396 function compareBoundaryPoints(h, r) { 6397 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6398 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6399 6400 // Check START_TO_START 6401 if (h === 0) 6402 return _compareBoundaryPoints(sc, so, rsc, rso); 6403 6404 // Check START_TO_END 6405 if (h === 1) 6406 return _compareBoundaryPoints(ec, eo, rsc, rso); 6407 6408 // Check END_TO_END 6409 if (h === 2) 6410 return _compareBoundaryPoints(ec, eo, rec, reo); 6411 6412 // Check END_TO_START 6413 if (h === 3) 6414 return _compareBoundaryPoints(sc, so, rec, reo); 6415 }; 6416 6417 function deleteContents() { 6418 _traverse(DELETE); 6419 }; 6420 6421 function extractContents() { 6422 return _traverse(EXTRACT); 6423 }; 6424 6425 function cloneContents() { 6426 return _traverse(CLONE); 6427 }; 6428 6429 function insertNode(n) { 6430 var startContainer = this[START_CONTAINER], 6431 startOffset = this[START_OFFSET], nn, o; 6432 6433 // Node is TEXT_NODE or CDATA 6434 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6435 if (!startOffset) { 6436 // At the start of text 6437 startContainer.parentNode.insertBefore(n, startContainer); 6438 } else if (startOffset >= startContainer.nodeValue.length) { 6439 // At the end of text 6440 dom.insertAfter(n, startContainer); 6441 } else { 6442 // Middle, need to split 6443 nn = startContainer.splitText(startOffset); 6444 startContainer.parentNode.insertBefore(n, nn); 6445 } 6446 } else { 6447 // Insert element node 6448 if (startContainer.childNodes.length > 0) 6449 o = startContainer.childNodes[startOffset]; 6450 6451 if (o) 6452 startContainer.insertBefore(n, o); 6453 else 6454 startContainer.appendChild(n); 6455 } 6456 }; 6457 6458 function surroundContents(n) { 6459 var f = t.extractContents(); 6460 6461 t.insertNode(n); 6462 n.appendChild(f); 6463 t.selectNode(n); 6464 }; 6465 6466 function cloneRange() { 6467 return extend(new Range(dom), { 6468 startContainer : t[START_CONTAINER], 6469 startOffset : t[START_OFFSET], 6470 endContainer : t[END_CONTAINER], 6471 endOffset : t[END_OFFSET], 6472 collapsed : t.collapsed, 6473 commonAncestorContainer : t.commonAncestorContainer 6474 }); 6475 }; 6476 6477 // Private methods 6478 6479 function _getSelectedNode(container, offset) { 6480 var child; 6481 6482 if (container.nodeType == 3 /* TEXT_NODE */) 6483 return container; 6484 6485 if (offset < 0) 6486 return container; 6487 6488 child = container.firstChild; 6489 while (child && offset > 0) { 6490 --offset; 6491 child = child.nextSibling; 6492 } 6493 6494 if (child) 6495 return child; 6496 6497 return container; 6498 }; 6499 6500 function _isCollapsed() { 6501 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6502 }; 6503 6504 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6505 var c, offsetC, n, cmnRoot, childA, childB; 6506 6507 // In the first case the boundary-points have the same container. A is before B 6508 // if its offset is less than the offset of B, A is equal to B if its offset is 6509 // equal to the offset of B, and A is after B if its offset is greater than the 6510 // offset of B. 6511 if (containerA == containerB) { 6512 if (offsetA == offsetB) 6513 return 0; // equal 6514 6515 if (offsetA < offsetB) 6516 return -1; // before 6517 6518 return 1; // after 6519 } 6520 6521 // In the second case a child node C of the container of A is an ancestor 6522 // container of B. In this case, A is before B if the offset of A is less than or 6523 // equal to the index of the child node C and A is after B otherwise. 6524 c = containerB; 6525 while (c && c.parentNode != containerA) 6526 c = c.parentNode; 6527 6528 if (c) { 6529 offsetC = 0; 6530 n = containerA.firstChild; 6531 6532 while (n != c && offsetC < offsetA) { 6533 offsetC++; 6534 n = n.nextSibling; 6535 } 6536 6537 if (offsetA <= offsetC) 6538 return -1; // before 6539 6540 return 1; // after 6541 } 6542 6543 // In the third case a child node C of the container of B is an ancestor container 6544 // of A. In this case, A is before B if the index of the child node C is less than 6545 // the offset of B and A is after B otherwise. 6546 c = containerA; 6547 while (c && c.parentNode != containerB) { 6548 c = c.parentNode; 6549 } 6550 6551 if (c) { 6552 offsetC = 0; 6553 n = containerB.firstChild; 6554 6555 while (n != c && offsetC < offsetB) { 6556 offsetC++; 6557 n = n.nextSibling; 6558 } 6559 6560 if (offsetC < offsetB) 6561 return -1; // before 6562 6563 return 1; // after 6564 } 6565 6566 // In the fourth case, none of three other cases hold: the containers of A and B 6567 // are siblings or descendants of sibling nodes. In this case, A is before B if 6568 // the container of A is before the container of B in a pre-order traversal of the 6569 // Ranges' context tree and A is after B otherwise. 6570 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6571 childA = containerA; 6572 6573 while (childA && childA.parentNode != cmnRoot) 6574 childA = childA.parentNode; 6575 6576 if (!childA) 6577 childA = cmnRoot; 6578 6579 childB = containerB; 6580 while (childB && childB.parentNode != cmnRoot) 6581 childB = childB.parentNode; 6582 6583 if (!childB) 6584 childB = cmnRoot; 6585 6586 if (childA == childB) 6587 return 0; // equal 6588 6589 n = cmnRoot.firstChild; 6590 while (n) { 6591 if (n == childA) 6592 return -1; // before 6593 6594 if (n == childB) 6595 return 1; // after 6596 6597 n = n.nextSibling; 6598 } 6599 }; 6600 6601 function _setEndPoint(st, n, o) { 6602 var ec, sc; 6603 6604 if (st) { 6605 t[START_CONTAINER] = n; 6606 t[START_OFFSET] = o; 6607 } else { 6608 t[END_CONTAINER] = n; 6609 t[END_OFFSET] = o; 6610 } 6611 6612 // If one boundary-point of a Range is set to have a root container 6613 // other than the current one for the Range, the Range is collapsed to 6614 // the new position. This enforces the restriction that both boundary- 6615 // points of a Range must have the same root container. 6616 ec = t[END_CONTAINER]; 6617 while (ec.parentNode) 6618 ec = ec.parentNode; 6619 6620 sc = t[START_CONTAINER]; 6621 while (sc.parentNode) 6622 sc = sc.parentNode; 6623 6624 if (sc == ec) { 6625 // The start position of a Range is guaranteed to never be after the 6626 // end position. To enforce this restriction, if the start is set to 6627 // be at a position after the end, the Range is collapsed to that 6628 // position. 6629 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6630 t.collapse(st); 6631 } else 6632 t.collapse(st); 6633 6634 t.collapsed = _isCollapsed(); 6635 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6636 }; 6637 6638 function _traverse(how) { 6639 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6640 6641 if (t[START_CONTAINER] == t[END_CONTAINER]) 6642 return _traverseSameContainer(how); 6643 6644 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6645 if (p == t[START_CONTAINER]) 6646 return _traverseCommonStartContainer(c, how); 6647 6648 ++endContainerDepth; 6649 } 6650 6651 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6652 if (p == t[END_CONTAINER]) 6653 return _traverseCommonEndContainer(c, how); 6654 6655 ++startContainerDepth; 6656 } 6657 6658 depthDiff = startContainerDepth - endContainerDepth; 6659 6660 startNode = t[START_CONTAINER]; 6661 while (depthDiff > 0) { 6662 startNode = startNode.parentNode; 6663 depthDiff--; 6664 } 6665 6666 endNode = t[END_CONTAINER]; 6667 while (depthDiff < 0) { 6668 endNode = endNode.parentNode; 6669 depthDiff++; 6670 } 6671 6672 // ascend the ancestor hierarchy until we have a common parent. 6673 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6674 startNode = sp; 6675 endNode = ep; 6676 } 6677 6678 return _traverseCommonAncestors(startNode, endNode, how); 6679 }; 6680 6681 function _traverseSameContainer(how) { 6682 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6683 6684 if (how != DELETE) 6685 frag = createDocumentFragment(); 6686 6687 // If selection is empty, just return the fragment 6688 if (t[START_OFFSET] == t[END_OFFSET]) 6689 return frag; 6690 6691 // Text node needs special case handling 6692 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6693 // get the substring 6694 s = t[START_CONTAINER].nodeValue; 6695 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6696 6697 // set the original text node to its new value 6698 if (how != CLONE) { 6699 n = t[START_CONTAINER]; 6700 start = t[START_OFFSET]; 6701 len = t[END_OFFSET] - t[START_OFFSET]; 6702 6703 if (start === 0 && len >= n.nodeValue.length - 1) { 6704 n.parentNode.removeChild(n); 6705 } else { 6706 n.deleteData(start, len); 6707 } 6708 6709 // Nothing is partially selected, so collapse to start point 6710 t.collapse(TRUE); 6711 } 6712 6713 if (how == DELETE) 6714 return; 6715 6716 if (sub.length > 0) { 6717 frag.appendChild(doc.createTextNode(sub)); 6718 } 6719 6720 return frag; 6721 } 6722 6723 // Copy nodes between the start/end offsets. 6724 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 6725 cnt = t[END_OFFSET] - t[START_OFFSET]; 6726 6727 while (n && cnt > 0) { 6728 sibling = n.nextSibling; 6729 xferNode = _traverseFullySelected(n, how); 6730 6731 if (frag) 6732 frag.appendChild( xferNode ); 6733 6734 --cnt; 6735 n = sibling; 6736 } 6737 6738 // Nothing is partially selected, so collapse to start point 6739 if (how != CLONE) 6740 t.collapse(TRUE); 6741 6742 return frag; 6743 }; 6744 6745 function _traverseCommonStartContainer(endAncestor, how) { 6746 var frag, n, endIdx, cnt, sibling, xferNode; 6747 6748 if (how != DELETE) 6749 frag = createDocumentFragment(); 6750 6751 n = _traverseRightBoundary(endAncestor, how); 6752 6753 if (frag) 6754 frag.appendChild(n); 6755 6756 endIdx = nodeIndex(endAncestor); 6757 cnt = endIdx - t[START_OFFSET]; 6758 6759 if (cnt <= 0) { 6760 // Collapse to just before the endAncestor, which 6761 // is partially selected. 6762 if (how != CLONE) { 6763 t.setEndBefore(endAncestor); 6764 t.collapse(FALSE); 6765 } 6766 6767 return frag; 6768 } 6769 6770 n = endAncestor.previousSibling; 6771 while (cnt > 0) { 6772 sibling = n.previousSibling; 6773 xferNode = _traverseFullySelected(n, how); 6774 6775 if (frag) 6776 frag.insertBefore(xferNode, frag.firstChild); 6777 6778 --cnt; 6779 n = sibling; 6780 } 6781 6782 // Collapse to just before the endAncestor, which 6783 // is partially selected. 6784 if (how != CLONE) { 6785 t.setEndBefore(endAncestor); 6786 t.collapse(FALSE); 6787 } 6788 6789 return frag; 6790 }; 6791 6792 function _traverseCommonEndContainer(startAncestor, how) { 6793 var frag, startIdx, n, cnt, sibling, xferNode; 6794 6795 if (how != DELETE) 6796 frag = createDocumentFragment(); 6797 6798 n = _traverseLeftBoundary(startAncestor, how); 6799 if (frag) 6800 frag.appendChild(n); 6801 6802 startIdx = nodeIndex(startAncestor); 6803 ++startIdx; // Because we already traversed it 6804 6805 cnt = t[END_OFFSET] - startIdx; 6806 n = startAncestor.nextSibling; 6807 while (n && cnt > 0) { 6808 sibling = n.nextSibling; 6809 xferNode = _traverseFullySelected(n, how); 6810 6811 if (frag) 6812 frag.appendChild(xferNode); 6813 6814 --cnt; 6815 n = sibling; 6816 } 6817 6818 if (how != CLONE) { 6819 t.setStartAfter(startAncestor); 6820 t.collapse(TRUE); 6821 } 6822 6823 return frag; 6824 }; 6825 6826 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 6827 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 6828 6829 if (how != DELETE) 6830 frag = createDocumentFragment(); 6831 6832 n = _traverseLeftBoundary(startAncestor, how); 6833 if (frag) 6834 frag.appendChild(n); 6835 6836 commonParent = startAncestor.parentNode; 6837 startOffset = nodeIndex(startAncestor); 6838 endOffset = nodeIndex(endAncestor); 6839 ++startOffset; 6840 6841 cnt = endOffset - startOffset; 6842 sibling = startAncestor.nextSibling; 6843 6844 while (cnt > 0) { 6845 nextSibling = sibling.nextSibling; 6846 n = _traverseFullySelected(sibling, how); 6847 6848 if (frag) 6849 frag.appendChild(n); 6850 6851 sibling = nextSibling; 6852 --cnt; 6853 } 6854 6855 n = _traverseRightBoundary(endAncestor, how); 6856 6857 if (frag) 6858 frag.appendChild(n); 6859 6860 if (how != CLONE) { 6861 t.setStartAfter(startAncestor); 6862 t.collapse(TRUE); 6863 } 6864 6865 return frag; 6866 }; 6867 6868 function _traverseRightBoundary(root, how) { 6869 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 6870 6871 if (next == root) 6872 return _traverseNode(next, isFullySelected, FALSE, how); 6873 6874 parent = next.parentNode; 6875 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 6876 6877 while (parent) { 6878 while (next) { 6879 prevSibling = next.previousSibling; 6880 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 6881 6882 if (how != DELETE) 6883 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 6884 6885 isFullySelected = TRUE; 6886 next = prevSibling; 6887 } 6888 6889 if (parent == root) 6890 return clonedParent; 6891 6892 next = parent.previousSibling; 6893 parent = parent.parentNode; 6894 6895 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 6896 6897 if (how != DELETE) 6898 clonedGrandParent.appendChild(clonedParent); 6899 6900 clonedParent = clonedGrandParent; 6901 } 6902 }; 6903 6904 function _traverseLeftBoundary(root, how) { 6905 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 6906 6907 if (next == root) 6908 return _traverseNode(next, isFullySelected, TRUE, how); 6909 6910 parent = next.parentNode; 6911 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 6912 6913 while (parent) { 6914 while (next) { 6915 nextSibling = next.nextSibling; 6916 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 6917 6918 if (how != DELETE) 6919 clonedParent.appendChild(clonedChild); 6920 6921 isFullySelected = TRUE; 6922 next = nextSibling; 6923 } 6924 6925 if (parent == root) 6926 return clonedParent; 6927 6928 next = parent.nextSibling; 6929 parent = parent.parentNode; 6930 6931 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 6932 6933 if (how != DELETE) 6934 clonedGrandParent.appendChild(clonedParent); 6935 6936 clonedParent = clonedGrandParent; 6937 } 6938 }; 6939 6940 function _traverseNode(n, isFullySelected, isLeft, how) { 6941 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 6942 6943 if (isFullySelected) 6944 return _traverseFullySelected(n, how); 6945 6946 if (n.nodeType == 3 /* TEXT_NODE */) { 6947 txtValue = n.nodeValue; 6948 6949 if (isLeft) { 6950 offset = t[START_OFFSET]; 6951 newNodeValue = txtValue.substring(offset); 6952 oldNodeValue = txtValue.substring(0, offset); 6953 } else { 6954 offset = t[END_OFFSET]; 6955 newNodeValue = txtValue.substring(0, offset); 6956 oldNodeValue = txtValue.substring(offset); 6957 } 6958 6959 if (how != CLONE) 6960 n.nodeValue = oldNodeValue; 6961 6962 if (how == DELETE) 6963 return; 6964 6965 newNode = dom.clone(n, FALSE); 6966 newNode.nodeValue = newNodeValue; 6967 6968 return newNode; 6969 } 6970 6971 if (how == DELETE) 6972 return; 6973 6974 return dom.clone(n, FALSE); 6975 }; 6976 6977 function _traverseFullySelected(n, how) { 6978 if (how != DELETE) 6979 return how == CLONE ? dom.clone(n, TRUE) : n; 6980 6981 n.parentNode.removeChild(n); 6982 }; 6983 6984 function toStringIE() { 6985 return dom.create('body', null, cloneContents()).outerText; 6986 } 6987 6988 return t; 6989 }; 6990 6991 ns.Range = Range; 6992 6993 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 6994 Range.prototype.toString = function() { 6995 return this.toStringIE(); 6996 }; 6997 })(tinymce.dom); 6998 6999 (function() { 7000 function Selection(selection) { 7001 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7002 7003 function getPosition(rng, start) { 7004 var checkRng, startIndex = 0, endIndex, inside, 7005 children, child, offset, index, position = -1, parent; 7006 7007 // Setup test range, collapse it and get the parent 7008 checkRng = rng.duplicate(); 7009 checkRng.collapse(start); 7010 parent = checkRng.parentElement(); 7011 7012 // Check if the selection is within the right document 7013 if (parent.ownerDocument !== selection.dom.doc) 7014 return; 7015 7016 // IE will report non editable elements as it's parent so look for an editable one 7017 while (parent.contentEditable === "false") { 7018 parent = parent.parentNode; 7019 } 7020 7021 // If parent doesn't have any children then return that we are inside the element 7022 if (!parent.hasChildNodes()) { 7023 return {node : parent, inside : 1}; 7024 } 7025 7026 // Setup node list and endIndex 7027 children = parent.children; 7028 endIndex = children.length - 1; 7029 7030 // Perform a binary search for the position 7031 while (startIndex <= endIndex) { 7032 index = Math.floor((startIndex + endIndex) / 2); 7033 7034 // Move selection to node and compare the ranges 7035 child = children[index]; 7036 checkRng.moveToElementText(child); 7037 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7038 7039 // Before/after or an exact match 7040 if (position > 0) { 7041 endIndex = index - 1; 7042 } else if (position < 0) { 7043 startIndex = index + 1; 7044 } else { 7045 return {node : child}; 7046 } 7047 } 7048 7049 // Check if child position is before or we didn't find a position 7050 if (position < 0) { 7051 // No element child was found use the parent element and the offset inside that 7052 if (!child) { 7053 checkRng.moveToElementText(parent); 7054 checkRng.collapse(true); 7055 child = parent; 7056 inside = true; 7057 } else 7058 checkRng.collapse(false); 7059 7060 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7061 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7062 offset = 0; 7063 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7064 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7065 break; 7066 } 7067 7068 offset++; 7069 } 7070 } else { 7071 // Child position is after the selection endpoint 7072 checkRng.collapse(true); 7073 7074 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7075 offset = 0; 7076 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7077 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7078 break; 7079 } 7080 7081 offset++; 7082 } 7083 } 7084 7085 return {node : child, position : position, offset : offset, inside : inside}; 7086 }; 7087 7088 // Returns a W3C DOM compatible range object by using the IE Range API 7089 function getRange() { 7090 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7091 7092 // If selection is outside the current document just return an empty range 7093 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7094 if (element.ownerDocument != dom.doc) 7095 return domRange; 7096 7097 collapsed = selection.isCollapsed(); 7098 7099 // Handle control selection 7100 if (ieRange.item) { 7101 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7102 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7103 7104 return domRange; 7105 } 7106 7107 function findEndPoint(start) { 7108 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7109 7110 container = endPoint.node; 7111 offset = endPoint.offset; 7112 7113 if (endPoint.inside && !container.hasChildNodes()) { 7114 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7115 return; 7116 } 7117 7118 if (offset === undef) { 7119 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7120 return; 7121 } 7122 7123 if (endPoint.position < 0) { 7124 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7125 7126 if (!sibling) { 7127 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7128 return; 7129 } 7130 7131 if (!offset) { 7132 if (sibling.nodeType == 3) 7133 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7134 else 7135 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7136 7137 return; 7138 } 7139 7140 // Find the text node and offset 7141 while (sibling) { 7142 nodeValue = sibling.nodeValue; 7143 textNodeOffset += nodeValue.length; 7144 7145 // We are at or passed the position we where looking for 7146 if (textNodeOffset >= offset) { 7147 container = sibling; 7148 textNodeOffset -= offset; 7149 textNodeOffset = nodeValue.length - textNodeOffset; 7150 break; 7151 } 7152 7153 sibling = sibling.nextSibling; 7154 } 7155 } else { 7156 // Find the text node and offset 7157 sibling = container.previousSibling; 7158 7159 if (!sibling) 7160 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7161 7162 // If there isn't any text to loop then use the first position 7163 if (!offset) { 7164 if (container.nodeType == 3) 7165 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7166 else 7167 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7168 7169 return; 7170 } 7171 7172 while (sibling) { 7173 textNodeOffset += sibling.nodeValue.length; 7174 7175 // We are at or passed the position we where looking for 7176 if (textNodeOffset >= offset) { 7177 container = sibling; 7178 textNodeOffset -= offset; 7179 break; 7180 } 7181 7182 sibling = sibling.previousSibling; 7183 } 7184 } 7185 7186 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7187 }; 7188 7189 try { 7190 // Find start point 7191 findEndPoint(true); 7192 7193 // Find end point if needed 7194 if (!collapsed) 7195 findEndPoint(); 7196 } catch (ex) { 7197 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7198 // access the nodeValue or other properties of text nodes. This seems to happend when 7199 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7200 if (ex.number == -2147024809) { 7201 // Get the current selection 7202 bookmark = self.getBookmark(2); 7203 7204 // Get start element 7205 tmpRange = ieRange.duplicate(); 7206 tmpRange.collapse(true); 7207 element = tmpRange.parentElement(); 7208 7209 // Get end element 7210 if (!collapsed) { 7211 tmpRange = ieRange.duplicate(); 7212 tmpRange.collapse(false); 7213 element2 = tmpRange.parentElement(); 7214 element2.innerHTML = element2.innerHTML; 7215 } 7216 7217 // Remove the broken elements 7218 element.innerHTML = element.innerHTML; 7219 7220 // Restore the selection 7221 self.moveToBookmark(bookmark); 7222 7223 // Since the range has moved we need to re-get it 7224 ieRange = selection.getRng(); 7225 7226 // Find start point 7227 findEndPoint(true); 7228 7229 // Find end point if needed 7230 if (!collapsed) 7231 findEndPoint(); 7232 } else 7233 throw ex; // Throw other errors 7234 } 7235 7236 return domRange; 7237 }; 7238 7239 this.getBookmark = function(type) { 7240 var rng = selection.getRng(), start, end, bookmark = {}; 7241 7242 function getIndexes(node) { 7243 var parent, root, children, i, indexes = []; 7244 7245 parent = node.parentNode; 7246 root = dom.getRoot().parentNode; 7247 7248 while (parent != root && parent.nodeType !== 9) { 7249 children = parent.children; 7250 7251 i = children.length; 7252 while (i--) { 7253 if (node === children[i]) { 7254 indexes.push(i); 7255 break; 7256 } 7257 } 7258 7259 node = parent; 7260 parent = parent.parentNode; 7261 } 7262 7263 return indexes; 7264 }; 7265 7266 function getBookmarkEndPoint(start) { 7267 var position; 7268 7269 position = getPosition(rng, start); 7270 if (position) { 7271 return { 7272 position : position.position, 7273 offset : position.offset, 7274 indexes : getIndexes(position.node), 7275 inside : position.inside 7276 }; 7277 } 7278 }; 7279 7280 // Non ubstructive bookmark 7281 if (type === 2) { 7282 // Handle text selection 7283 if (!rng.item) { 7284 bookmark.start = getBookmarkEndPoint(true); 7285 7286 if (!selection.isCollapsed()) 7287 bookmark.end = getBookmarkEndPoint(); 7288 } else 7289 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7290 } 7291 7292 return bookmark; 7293 }; 7294 7295 this.moveToBookmark = function(bookmark) { 7296 var rng, body = dom.doc.body; 7297 7298 function resolveIndexes(indexes) { 7299 var node, i, idx, children; 7300 7301 node = dom.getRoot(); 7302 for (i = indexes.length - 1; i >= 0; i--) { 7303 children = node.children; 7304 idx = indexes[i]; 7305 7306 if (idx <= children.length - 1) { 7307 node = children[idx]; 7308 } 7309 } 7310 7311 return node; 7312 }; 7313 7314 function setBookmarkEndPoint(start) { 7315 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7316 7317 if (endPoint) { 7318 moveLeft = endPoint.position > 0; 7319 7320 moveRng = body.createTextRange(); 7321 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7322 7323 offset = endPoint.offset; 7324 if (offset !== undef) { 7325 moveRng.collapse(endPoint.inside || moveLeft); 7326 moveRng.moveStart('character', moveLeft ? -offset : offset); 7327 } else 7328 moveRng.collapse(start); 7329 7330 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7331 7332 if (start) 7333 rng.collapse(true); 7334 } 7335 }; 7336 7337 if (bookmark.start) { 7338 if (bookmark.start.ctrl) { 7339 rng = body.createControlRange(); 7340 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7341 rng.select(); 7342 } else { 7343 rng = body.createTextRange(); 7344 setBookmarkEndPoint(true); 7345 setBookmarkEndPoint(); 7346 rng.select(); 7347 } 7348 } 7349 }; 7350 7351 this.addRange = function(rng) { 7352 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body; 7353 7354 function setEndPoint(start) { 7355 var container, offset, marker, tmpRng, nodes; 7356 7357 marker = dom.create('a'); 7358 container = start ? startContainer : endContainer; 7359 offset = start ? startOffset : endOffset; 7360 tmpRng = ieRng.duplicate(); 7361 7362 if (container == doc || container == doc.documentElement) { 7363 container = body; 7364 offset = 0; 7365 } 7366 7367 if (container.nodeType == 3) { 7368 container.parentNode.insertBefore(marker, container); 7369 tmpRng.moveToElementText(marker); 7370 tmpRng.moveStart('character', offset); 7371 dom.remove(marker); 7372 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7373 } else { 7374 nodes = container.childNodes; 7375 7376 if (nodes.length) { 7377 if (offset >= nodes.length) { 7378 dom.insertAfter(marker, nodes[nodes.length - 1]); 7379 } else { 7380 container.insertBefore(marker, nodes[offset]); 7381 } 7382 7383 tmpRng.moveToElementText(marker); 7384 } else if (container.canHaveHTML) { 7385 // Empty node selection for example <div>|</div> 7386 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7387 container.innerHTML = '<span>\uFEFF</span>'; 7388 marker = container.firstChild; 7389 tmpRng.moveToElementText(marker); 7390 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7391 } 7392 7393 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7394 dom.remove(marker); 7395 } 7396 } 7397 7398 // Setup some shorter versions 7399 startContainer = rng.startContainer; 7400 startOffset = rng.startOffset; 7401 endContainer = rng.endContainer; 7402 endOffset = rng.endOffset; 7403 ieRng = body.createTextRange(); 7404 7405 // If single element selection then try making a control selection out of it 7406 if (startContainer == endContainer && startContainer.nodeType == 1) { 7407 // Trick to place the caret inside an empty block element like <p></p> 7408 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7409 if (startContainer.canHaveHTML) { 7410 // Check if previous sibling is an empty block if it is then we need to render it 7411 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7412 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7413 sibling = startContainer.previousSibling; 7414 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7415 sibling.innerHTML = '\uFEFF'; 7416 } else { 7417 sibling = null; 7418 } 7419 7420 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7421 ieRng.moveToElementText(startContainer.lastChild); 7422 ieRng.select(); 7423 dom.doc.selection.clear(); 7424 startContainer.innerHTML = ''; 7425 7426 if (sibling) { 7427 sibling.innerHTML = ''; 7428 } 7429 return; 7430 } else { 7431 startOffset = dom.nodeIndex(startContainer); 7432 startContainer = startContainer.parentNode; 7433 } 7434 } 7435 7436 if (startOffset == endOffset - 1) { 7437 try { 7438 ctrlRng = body.createControlRange(); 7439 ctrlRng.addElement(startContainer.childNodes[startOffset]); 7440 ctrlRng.select(); 7441 return; 7442 } catch (ex) { 7443 // Ignore 7444 } 7445 } 7446 } 7447 7448 // Set start/end point of selection 7449 setEndPoint(true); 7450 setEndPoint(); 7451 7452 // Select the new range and scroll it into view 7453 ieRng.select(); 7454 }; 7455 7456 // Expose range method 7457 this.getRangeAt = getRange; 7458 }; 7459 7460 // Expose the selection object 7461 tinymce.dom.TridentSelection = Selection; 7462 })(); 7463 7464 7465 /* 7466 * Sizzle CSS Selector Engine 7467 * Copyright, The Dojo Foundation 7468 * Released under the MIT, BSD, and GPL Licenses. 7469 * More information: http://sizzlejs.com/ 7470 */ 7471 (function(){ 7472 7473 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7474 expando = "sizcache", 7475 done = 0, 7476 toString = Object.prototype.toString, 7477 hasDuplicate = false, 7478 baseHasDuplicate = true, 7479 rBackslash = /\\/g, 7480 rReturn = /\r\n/g, 7481 rNonWord = /\W/; 7482 7483 // Here we check if the JavaScript engine is using some sort of 7484 // optimization where it does not always call our comparision 7485 // function. If that is the case, discard the hasDuplicate value. 7486 // Thus far that includes Google Chrome. 7487 [0, 0].sort(function() { 7488 baseHasDuplicate = false; 7489 return 0; 7490 }); 7491 7492 var Sizzle = function( selector, context, results, seed ) { 7493 results = results || []; 7494 context = context || document; 7495 7496 var origContext = context; 7497 7498 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7499 return []; 7500 } 7501 7502 if ( !selector || typeof selector !== "string" ) { 7503 return results; 7504 } 7505 7506 var m, set, checkSet, extra, ret, cur, pop, i, 7507 prune = true, 7508 contextXML = Sizzle.isXML( context ), 7509 parts = [], 7510 soFar = selector; 7511 7512 // Reset the position of the chunker regexp (start from head) 7513 do { 7514 chunker.exec( "" ); 7515 m = chunker.exec( soFar ); 7516 7517 if ( m ) { 7518 soFar = m[3]; 7519 7520 parts.push( m[1] ); 7521 7522 if ( m[2] ) { 7523 extra = m[3]; 7524 break; 7525 } 7526 } 7527 } while ( m ); 7528 7529 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7530 7531 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7532 set = posProcess( parts[0] + parts[1], context, seed ); 7533 7534 } else { 7535 set = Expr.relative[ parts[0] ] ? 7536 [ context ] : 7537 Sizzle( parts.shift(), context ); 7538 7539 while ( parts.length ) { 7540 selector = parts.shift(); 7541 7542 if ( Expr.relative[ selector ] ) { 7543 selector += parts.shift(); 7544 } 7545 7546 set = posProcess( selector, set, seed ); 7547 } 7548 } 7549 7550 } else { 7551 // Take a shortcut and set the context if the root selector is an ID 7552 // (but not if it'll be faster if the inner selector is an ID) 7553 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7554 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7555 7556 ret = Sizzle.find( parts.shift(), context, contextXML ); 7557 context = ret.expr ? 7558 Sizzle.filter( ret.expr, ret.set )[0] : 7559 ret.set[0]; 7560 } 7561 7562 if ( context ) { 7563 ret = seed ? 7564 { expr: parts.pop(), set: makeArray(seed) } : 7565 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7566 7567 set = ret.expr ? 7568 Sizzle.filter( ret.expr, ret.set ) : 7569 ret.set; 7570 7571 if ( parts.length > 0 ) { 7572 checkSet = makeArray( set ); 7573 7574 } else { 7575 prune = false; 7576 } 7577 7578 while ( parts.length ) { 7579 cur = parts.pop(); 7580 pop = cur; 7581 7582 if ( !Expr.relative[ cur ] ) { 7583 cur = ""; 7584 } else { 7585 pop = parts.pop(); 7586 } 7587 7588 if ( pop == null ) { 7589 pop = context; 7590 } 7591 7592 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7593 } 7594 7595 } else { 7596 checkSet = parts = []; 7597 } 7598 } 7599 7600 if ( !checkSet ) { 7601 checkSet = set; 7602 } 7603 7604 if ( !checkSet ) { 7605 Sizzle.error( cur || selector ); 7606 } 7607 7608 if ( toString.call(checkSet) === "[object Array]" ) { 7609 if ( !prune ) { 7610 results.push.apply( results, checkSet ); 7611 7612 } else if ( context && context.nodeType === 1 ) { 7613 for ( i = 0; checkSet[i] != null; i++ ) { 7614 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7615 results.push( set[i] ); 7616 } 7617 } 7618 7619 } else { 7620 for ( i = 0; checkSet[i] != null; i++ ) { 7621 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7622 results.push( set[i] ); 7623 } 7624 } 7625 } 7626 7627 } else { 7628 makeArray( checkSet, results ); 7629 } 7630 7631 if ( extra ) { 7632 Sizzle( extra, origContext, results, seed ); 7633 Sizzle.uniqueSort( results ); 7634 } 7635 7636 return results; 7637 }; 7638 7639 Sizzle.uniqueSort = function( results ) { 7640 if ( sortOrder ) { 7641 hasDuplicate = baseHasDuplicate; 7642 results.sort( sortOrder ); 7643 7644 if ( hasDuplicate ) { 7645 for ( var i = 1; i < results.length; i++ ) { 7646 if ( results[i] === results[ i - 1 ] ) { 7647 results.splice( i--, 1 ); 7648 } 7649 } 7650 } 7651 } 7652 7653 return results; 7654 }; 7655 7656 Sizzle.matches = function( expr, set ) { 7657 return Sizzle( expr, null, null, set ); 7658 }; 7659 7660 Sizzle.matchesSelector = function( node, expr ) { 7661 return Sizzle( expr, null, null, [node] ).length > 0; 7662 }; 7663 7664 Sizzle.find = function( expr, context, isXML ) { 7665 var set, i, len, match, type, left; 7666 7667 if ( !expr ) { 7668 return []; 7669 } 7670 7671 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7672 type = Expr.order[i]; 7673 7674 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7675 left = match[1]; 7676 match.splice( 1, 1 ); 7677 7678 if ( left.substr( left.length - 1 ) !== "\\" ) { 7679 match[1] = (match[1] || "").replace( rBackslash, "" ); 7680 set = Expr.find[ type ]( match, context, isXML ); 7681 7682 if ( set != null ) { 7683 expr = expr.replace( Expr.match[ type ], "" ); 7684 break; 7685 } 7686 } 7687 } 7688 } 7689 7690 if ( !set ) { 7691 set = typeof context.getElementsByTagName !== "undefined" ? 7692 context.getElementsByTagName( "*" ) : 7693 []; 7694 } 7695 7696 return { set: set, expr: expr }; 7697 }; 7698 7699 Sizzle.filter = function( expr, set, inplace, not ) { 7700 var match, anyFound, 7701 type, found, item, filter, left, 7702 i, pass, 7703 old = expr, 7704 result = [], 7705 curLoop = set, 7706 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 7707 7708 while ( expr && set.length ) { 7709 for ( type in Expr.filter ) { 7710 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 7711 filter = Expr.filter[ type ]; 7712 left = match[1]; 7713 7714 anyFound = false; 7715 7716 match.splice(1,1); 7717 7718 if ( left.substr( left.length - 1 ) === "\\" ) { 7719 continue; 7720 } 7721 7722 if ( curLoop === result ) { 7723 result = []; 7724 } 7725 7726 if ( Expr.preFilter[ type ] ) { 7727 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 7728 7729 if ( !match ) { 7730 anyFound = found = true; 7731 7732 } else if ( match === true ) { 7733 continue; 7734 } 7735 } 7736 7737 if ( match ) { 7738 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 7739 if ( item ) { 7740 found = filter( item, match, i, curLoop ); 7741 pass = not ^ found; 7742 7743 if ( inplace && found != null ) { 7744 if ( pass ) { 7745 anyFound = true; 7746 7747 } else { 7748 curLoop[i] = false; 7749 } 7750 7751 } else if ( pass ) { 7752 result.push( item ); 7753 anyFound = true; 7754 } 7755 } 7756 } 7757 } 7758 7759 if ( found !== undefined ) { 7760 if ( !inplace ) { 7761 curLoop = result; 7762 } 7763 7764 expr = expr.replace( Expr.match[ type ], "" ); 7765 7766 if ( !anyFound ) { 7767 return []; 7768 } 7769 7770 break; 7771 } 7772 } 7773 } 7774 7775 // Improper expression 7776 if ( expr === old ) { 7777 if ( anyFound == null ) { 7778 Sizzle.error( expr ); 7779 7780 } else { 7781 break; 7782 } 7783 } 7784 7785 old = expr; 7786 } 7787 7788 return curLoop; 7789 }; 7790 7791 Sizzle.error = function( msg ) { 7792 throw new Error( "Syntax error, unrecognized expression: " + msg ); 7793 }; 7794 7795 var getText = Sizzle.getText = function( elem ) { 7796 var i, node, 7797 nodeType = elem.nodeType, 7798 ret = ""; 7799 7800 if ( nodeType ) { 7801 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 7802 // Use textContent || innerText for elements 7803 if ( typeof elem.textContent === 'string' ) { 7804 return elem.textContent; 7805 } else if ( typeof elem.innerText === 'string' ) { 7806 // Replace IE's carriage returns 7807 return elem.innerText.replace( rReturn, '' ); 7808 } else { 7809 // Traverse it's children 7810 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 7811 ret += getText( elem ); 7812 } 7813 } 7814 } else if ( nodeType === 3 || nodeType === 4 ) { 7815 return elem.nodeValue; 7816 } 7817 } else { 7818 7819 // If no nodeType, this is expected to be an array 7820 for ( i = 0; (node = elem[i]); i++ ) { 7821 // Do not traverse comment nodes 7822 if ( node.nodeType !== 8 ) { 7823 ret += getText( node ); 7824 } 7825 } 7826 } 7827 return ret; 7828 }; 7829 7830 var Expr = Sizzle.selectors = { 7831 order: [ "ID", "NAME", "TAG" ], 7832 7833 match: { 7834 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7835 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7836 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 7837 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 7838 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 7839 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 7840 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 7841 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 7842 }, 7843 7844 leftMatch: {}, 7845 7846 attrMap: { 7847 "class": "className", 7848 "for": "htmlFor" 7849 }, 7850 7851 attrHandle: { 7852 href: function( elem ) { 7853 return elem.getAttribute( "href" ); 7854 }, 7855 type: function( elem ) { 7856 return elem.getAttribute( "type" ); 7857 } 7858 }, 7859 7860 relative: { 7861 "+": function(checkSet, part){ 7862 var isPartStr = typeof part === "string", 7863 isTag = isPartStr && !rNonWord.test( part ), 7864 isPartStrNotTag = isPartStr && !isTag; 7865 7866 if ( isTag ) { 7867 part = part.toLowerCase(); 7868 } 7869 7870 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 7871 if ( (elem = checkSet[i]) ) { 7872 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 7873 7874 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 7875 elem || false : 7876 elem === part; 7877 } 7878 } 7879 7880 if ( isPartStrNotTag ) { 7881 Sizzle.filter( part, checkSet, true ); 7882 } 7883 }, 7884 7885 ">": function( checkSet, part ) { 7886 var elem, 7887 isPartStr = typeof part === "string", 7888 i = 0, 7889 l = checkSet.length; 7890 7891 if ( isPartStr && !rNonWord.test( part ) ) { 7892 part = part.toLowerCase(); 7893 7894 for ( ; i < l; i++ ) { 7895 elem = checkSet[i]; 7896 7897 if ( elem ) { 7898 var parent = elem.parentNode; 7899 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 7900 } 7901 } 7902 7903 } else { 7904 for ( ; i < l; i++ ) { 7905 elem = checkSet[i]; 7906 7907 if ( elem ) { 7908 checkSet[i] = isPartStr ? 7909 elem.parentNode : 7910 elem.parentNode === part; 7911 } 7912 } 7913 7914 if ( isPartStr ) { 7915 Sizzle.filter( part, checkSet, true ); 7916 } 7917 } 7918 }, 7919 7920 "": function(checkSet, part, isXML){ 7921 var nodeCheck, 7922 doneName = done++, 7923 checkFn = dirCheck; 7924 7925 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7926 part = part.toLowerCase(); 7927 nodeCheck = part; 7928 checkFn = dirNodeCheck; 7929 } 7930 7931 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 7932 }, 7933 7934 "~": function( checkSet, part, isXML ) { 7935 var nodeCheck, 7936 doneName = done++, 7937 checkFn = dirCheck; 7938 7939 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7940 part = part.toLowerCase(); 7941 nodeCheck = part; 7942 checkFn = dirNodeCheck; 7943 } 7944 7945 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 7946 } 7947 }, 7948 7949 find: { 7950 ID: function( match, context, isXML ) { 7951 if ( typeof context.getElementById !== "undefined" && !isXML ) { 7952 var m = context.getElementById(match[1]); 7953 // Check parentNode to catch when Blackberry 4.6 returns 7954 // nodes that are no longer in the document #6963 7955 return m && m.parentNode ? [m] : []; 7956 } 7957 }, 7958 7959 NAME: function( match, context ) { 7960 if ( typeof context.getElementsByName !== "undefined" ) { 7961 var ret = [], 7962 results = context.getElementsByName( match[1] ); 7963 7964 for ( var i = 0, l = results.length; i < l; i++ ) { 7965 if ( results[i].getAttribute("name") === match[1] ) { 7966 ret.push( results[i] ); 7967 } 7968 } 7969 7970 return ret.length === 0 ? null : ret; 7971 } 7972 }, 7973 7974 TAG: function( match, context ) { 7975 if ( typeof context.getElementsByTagName !== "undefined" ) { 7976 return context.getElementsByTagName( match[1] ); 7977 } 7978 } 7979 }, 7980 preFilter: { 7981 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 7982 match = " " + match[1].replace( rBackslash, "" ) + " "; 7983 7984 if ( isXML ) { 7985 return match; 7986 } 7987 7988 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 7989 if ( elem ) { 7990 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 7991 if ( !inplace ) { 7992 result.push( elem ); 7993 } 7994 7995 } else if ( inplace ) { 7996 curLoop[i] = false; 7997 } 7998 } 7999 } 8000 8001 return false; 8002 }, 8003 8004 ID: function( match ) { 8005 return match[1].replace( rBackslash, "" ); 8006 }, 8007 8008 TAG: function( match, curLoop ) { 8009 return match[1].replace( rBackslash, "" ).toLowerCase(); 8010 }, 8011 8012 CHILD: function( match ) { 8013 if ( match[1] === "nth" ) { 8014 if ( !match[2] ) { 8015 Sizzle.error( match[0] ); 8016 } 8017 8018 match[2] = match[2].replace(/^\+|\s*/g, ''); 8019 8020 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8021 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8022 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8023 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8024 8025 // calculate the numbers (first)n+(last) including if they are negative 8026 match[2] = (test[1] + (test[2] || 1)) - 0; 8027 match[3] = test[3] - 0; 8028 } 8029 else if ( match[2] ) { 8030 Sizzle.error( match[0] ); 8031 } 8032 8033 // TODO: Move to normal caching system 8034 match[0] = done++; 8035 8036 return match; 8037 }, 8038 8039 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8040 var name = match[1] = match[1].replace( rBackslash, "" ); 8041 8042 if ( !isXML && Expr.attrMap[name] ) { 8043 match[1] = Expr.attrMap[name]; 8044 } 8045 8046 // Handle if an un-quoted value was used 8047 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8048 8049 if ( match[2] === "~=" ) { 8050 match[4] = " " + match[4] + " "; 8051 } 8052 8053 return match; 8054 }, 8055 8056 PSEUDO: function( match, curLoop, inplace, result, not ) { 8057 if ( match[1] === "not" ) { 8058 // If we're dealing with a complex expression, or a simple one 8059 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8060 match[3] = Sizzle(match[3], null, null, curLoop); 8061 8062 } else { 8063 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8064 8065 if ( !inplace ) { 8066 result.push.apply( result, ret ); 8067 } 8068 8069 return false; 8070 } 8071 8072 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8073 return true; 8074 } 8075 8076 return match; 8077 }, 8078 8079 POS: function( match ) { 8080 match.unshift( true ); 8081 8082 return match; 8083 } 8084 }, 8085 8086 filters: { 8087 enabled: function( elem ) { 8088 return elem.disabled === false && elem.type !== "hidden"; 8089 }, 8090 8091 disabled: function( elem ) { 8092 return elem.disabled === true; 8093 }, 8094 8095 checked: function( elem ) { 8096 return elem.checked === true; 8097 }, 8098 8099 selected: function( elem ) { 8100 // Accessing this property makes selected-by-default 8101 // options in Safari work properly 8102 if ( elem.parentNode ) { 8103 elem.parentNode.selectedIndex; 8104 } 8105 8106 return elem.selected === true; 8107 }, 8108 8109 parent: function( elem ) { 8110 return !!elem.firstChild; 8111 }, 8112 8113 empty: function( elem ) { 8114 return !elem.firstChild; 8115 }, 8116 8117 has: function( elem, i, match ) { 8118 return !!Sizzle( match[3], elem ).length; 8119 }, 8120 8121 header: function( elem ) { 8122 return (/h\d/i).test( elem.nodeName ); 8123 }, 8124 8125 text: function( elem ) { 8126 var attr = elem.getAttribute( "type" ), type = elem.type; 8127 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8128 // use getAttribute instead to test this case 8129 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8130 }, 8131 8132 radio: function( elem ) { 8133 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8134 }, 8135 8136 checkbox: function( elem ) { 8137 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8138 }, 8139 8140 file: function( elem ) { 8141 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8142 }, 8143 8144 password: function( elem ) { 8145 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8146 }, 8147 8148 submit: function( elem ) { 8149 var name = elem.nodeName.toLowerCase(); 8150 return (name === "input" || name === "button") && "submit" === elem.type; 8151 }, 8152 8153 image: function( elem ) { 8154 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8155 }, 8156 8157 reset: function( elem ) { 8158 var name = elem.nodeName.toLowerCase(); 8159 return (name === "input" || name === "button") && "reset" === elem.type; 8160 }, 8161 8162 button: function( elem ) { 8163 var name = elem.nodeName.toLowerCase(); 8164 return name === "input" && "button" === elem.type || name === "button"; 8165 }, 8166 8167 input: function( elem ) { 8168 return (/input|select|textarea|button/i).test( elem.nodeName ); 8169 }, 8170 8171 focus: function( elem ) { 8172 return elem === elem.ownerDocument.activeElement; 8173 } 8174 }, 8175 setFilters: { 8176 first: function( elem, i ) { 8177 return i === 0; 8178 }, 8179 8180 last: function( elem, i, match, array ) { 8181 return i === array.length - 1; 8182 }, 8183 8184 even: function( elem, i ) { 8185 return i % 2 === 0; 8186 }, 8187 8188 odd: function( elem, i ) { 8189 return i % 2 === 1; 8190 }, 8191 8192 lt: function( elem, i, match ) { 8193 return i < match[3] - 0; 8194 }, 8195 8196 gt: function( elem, i, match ) { 8197 return i > match[3] - 0; 8198 }, 8199 8200 nth: function( elem, i, match ) { 8201 return match[3] - 0 === i; 8202 }, 8203 8204 eq: function( elem, i, match ) { 8205 return match[3] - 0 === i; 8206 } 8207 }, 8208 filter: { 8209 PSEUDO: function( elem, match, i, array ) { 8210 var name = match[1], 8211 filter = Expr.filters[ name ]; 8212 8213 if ( filter ) { 8214 return filter( elem, i, match, array ); 8215 8216 } else if ( name === "contains" ) { 8217 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8218 8219 } else if ( name === "not" ) { 8220 var not = match[3]; 8221 8222 for ( var j = 0, l = not.length; j < l; j++ ) { 8223 if ( not[j] === elem ) { 8224 return false; 8225 } 8226 } 8227 8228 return true; 8229 8230 } else { 8231 Sizzle.error( name ); 8232 } 8233 }, 8234 8235 CHILD: function( elem, match ) { 8236 var first, last, 8237 doneName, parent, cache, 8238 count, diff, 8239 type = match[1], 8240 node = elem; 8241 8242 switch ( type ) { 8243 case "only": 8244 case "first": 8245 while ( (node = node.previousSibling) ) { 8246 if ( node.nodeType === 1 ) { 8247 return false; 8248 } 8249 } 8250 8251 if ( type === "first" ) { 8252 return true; 8253 } 8254 8255 node = elem; 8256 8257 /* falls through */ 8258 case "last": 8259 while ( (node = node.nextSibling) ) { 8260 if ( node.nodeType === 1 ) { 8261 return false; 8262 } 8263 } 8264 8265 return true; 8266 8267 case "nth": 8268 first = match[2]; 8269 last = match[3]; 8270 8271 if ( first === 1 && last === 0 ) { 8272 return true; 8273 } 8274 8275 doneName = match[0]; 8276 parent = elem.parentNode; 8277 8278 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8279 count = 0; 8280 8281 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8282 if ( node.nodeType === 1 ) { 8283 node.nodeIndex = ++count; 8284 } 8285 } 8286 8287 parent[ expando ] = doneName; 8288 } 8289 8290 diff = elem.nodeIndex - last; 8291 8292 if ( first === 0 ) { 8293 return diff === 0; 8294 8295 } else { 8296 return ( diff % first === 0 && diff / first >= 0 ); 8297 } 8298 } 8299 }, 8300 8301 ID: function( elem, match ) { 8302 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8303 }, 8304 8305 TAG: function( elem, match ) { 8306 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8307 }, 8308 8309 CLASS: function( elem, match ) { 8310 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8311 .indexOf( match ) > -1; 8312 }, 8313 8314 ATTR: function( elem, match ) { 8315 var name = match[1], 8316 result = Sizzle.attr ? 8317 Sizzle.attr( elem, name ) : 8318 Expr.attrHandle[ name ] ? 8319 Expr.attrHandle[ name ]( elem ) : 8320 elem[ name ] != null ? 8321 elem[ name ] : 8322 elem.getAttribute( name ), 8323 value = result + "", 8324 type = match[2], 8325 check = match[4]; 8326 8327 return result == null ? 8328 type === "!=" : 8329 !type && Sizzle.attr ? 8330 result != null : 8331 type === "=" ? 8332 value === check : 8333 type === "*=" ? 8334 value.indexOf(check) >= 0 : 8335 type === "~=" ? 8336 (" " + value + " ").indexOf(check) >= 0 : 8337 !check ? 8338 value && result !== false : 8339 type === "!=" ? 8340 value !== check : 8341 type === "^=" ? 8342 value.indexOf(check) === 0 : 8343 type === "$=" ? 8344 value.substr(value.length - check.length) === check : 8345 type === "|=" ? 8346 value === check || value.substr(0, check.length + 1) === check + "-" : 8347 false; 8348 }, 8349 8350 POS: function( elem, match, i, array ) { 8351 var name = match[2], 8352 filter = Expr.setFilters[ name ]; 8353 8354 if ( filter ) { 8355 return filter( elem, i, match, array ); 8356 } 8357 } 8358 } 8359 }; 8360 8361 var origPOS = Expr.match.POS, 8362 fescape = function(all, num){ 8363 return "\\" + (num - 0 + 1); 8364 }; 8365 8366 for ( var type in Expr.match ) { 8367 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8368 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8369 } 8370 // Expose origPOS 8371 // "global" as in regardless of relation to brackets/parens 8372 Expr.match.globalPOS = origPOS; 8373 8374 var makeArray = function( array, results ) { 8375 array = Array.prototype.slice.call( array, 0 ); 8376 8377 if ( results ) { 8378 results.push.apply( results, array ); 8379 return results; 8380 } 8381 8382 return array; 8383 }; 8384 8385 // Perform a simple check to determine if the browser is capable of 8386 // converting a NodeList to an array using builtin methods. 8387 // Also verifies that the returned array holds DOM nodes 8388 // (which is not the case in the Blackberry browser) 8389 try { 8390 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8391 8392 // Provide a fallback method if it does not work 8393 } catch( e ) { 8394 makeArray = function( array, results ) { 8395 var i = 0, 8396 ret = results || []; 8397 8398 if ( toString.call(array) === "[object Array]" ) { 8399 Array.prototype.push.apply( ret, array ); 8400 8401 } else { 8402 if ( typeof array.length === "number" ) { 8403 for ( var l = array.length; i < l; i++ ) { 8404 ret.push( array[i] ); 8405 } 8406 8407 } else { 8408 for ( ; array[i]; i++ ) { 8409 ret.push( array[i] ); 8410 } 8411 } 8412 } 8413 8414 return ret; 8415 }; 8416 } 8417 8418 var sortOrder, siblingCheck; 8419 8420 if ( document.documentElement.compareDocumentPosition ) { 8421 sortOrder = function( a, b ) { 8422 if ( a === b ) { 8423 hasDuplicate = true; 8424 return 0; 8425 } 8426 8427 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8428 return a.compareDocumentPosition ? -1 : 1; 8429 } 8430 8431 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8432 }; 8433 8434 } else { 8435 sortOrder = function( a, b ) { 8436 // The nodes are identical, we can exit early 8437 if ( a === b ) { 8438 hasDuplicate = true; 8439 return 0; 8440 8441 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8442 } else if ( a.sourceIndex && b.sourceIndex ) { 8443 return a.sourceIndex - b.sourceIndex; 8444 } 8445 8446 var al, bl, 8447 ap = [], 8448 bp = [], 8449 aup = a.parentNode, 8450 bup = b.parentNode, 8451 cur = aup; 8452 8453 // If the nodes are siblings (or identical) we can do a quick check 8454 if ( aup === bup ) { 8455 return siblingCheck( a, b ); 8456 8457 // If no parents were found then the nodes are disconnected 8458 } else if ( !aup ) { 8459 return -1; 8460 8461 } else if ( !bup ) { 8462 return 1; 8463 } 8464 8465 // Otherwise they're somewhere else in the tree so we need 8466 // to build up a full list of the parentNodes for comparison 8467 while ( cur ) { 8468 ap.unshift( cur ); 8469 cur = cur.parentNode; 8470 } 8471 8472 cur = bup; 8473 8474 while ( cur ) { 8475 bp.unshift( cur ); 8476 cur = cur.parentNode; 8477 } 8478 8479 al = ap.length; 8480 bl = bp.length; 8481 8482 // Start walking down the tree looking for a discrepancy 8483 for ( var i = 0; i < al && i < bl; i++ ) { 8484 if ( ap[i] !== bp[i] ) { 8485 return siblingCheck( ap[i], bp[i] ); 8486 } 8487 } 8488 8489 // We ended someplace up the tree so do a sibling check 8490 return i === al ? 8491 siblingCheck( a, bp[i], -1 ) : 8492 siblingCheck( ap[i], b, 1 ); 8493 }; 8494 8495 siblingCheck = function( a, b, ret ) { 8496 if ( a === b ) { 8497 return ret; 8498 } 8499 8500 var cur = a.nextSibling; 8501 8502 while ( cur ) { 8503 if ( cur === b ) { 8504 return -1; 8505 } 8506 8507 cur = cur.nextSibling; 8508 } 8509 8510 return 1; 8511 }; 8512 } 8513 8514 // Check to see if the browser returns elements by name when 8515 // querying by getElementById (and provide a workaround) 8516 (function(){ 8517 // We're going to inject a fake input element with a specified name 8518 var form = document.createElement("div"), 8519 id = "script" + (new Date()).getTime(), 8520 root = document.documentElement; 8521 8522 form.innerHTML = "<a name='" + id + "'/>"; 8523 8524 // Inject it into the root element, check its status, and remove it quickly 8525 root.insertBefore( form, root.firstChild ); 8526 8527 // The workaround has to do additional checks after a getElementById 8528 // Which slows things down for other browsers (hence the branching) 8529 if ( document.getElementById( id ) ) { 8530 Expr.find.ID = function( match, context, isXML ) { 8531 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8532 var m = context.getElementById(match[1]); 8533 8534 return m ? 8535 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8536 [m] : 8537 undefined : 8538 []; 8539 } 8540 }; 8541 8542 Expr.filter.ID = function( elem, match ) { 8543 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8544 8545 return elem.nodeType === 1 && node && node.nodeValue === match; 8546 }; 8547 } 8548 8549 root.removeChild( form ); 8550 8551 // release memory in IE 8552 root = form = null; 8553 })(); 8554 8555 (function(){ 8556 // Check to see if the browser returns only elements 8557 // when doing getElementsByTagName("*") 8558 8559 // Create a fake element 8560 var div = document.createElement("div"); 8561 div.appendChild( document.createComment("") ); 8562 8563 // Make sure no comments are found 8564 if ( div.getElementsByTagName("*").length > 0 ) { 8565 Expr.find.TAG = function( match, context ) { 8566 var results = context.getElementsByTagName( match[1] ); 8567 8568 // Filter out possible comments 8569 if ( match[1] === "*" ) { 8570 var tmp = []; 8571 8572 for ( var i = 0; results[i]; i++ ) { 8573 if ( results[i].nodeType === 1 ) { 8574 tmp.push( results[i] ); 8575 } 8576 } 8577 8578 results = tmp; 8579 } 8580 8581 return results; 8582 }; 8583 } 8584 8585 // Check to see if an attribute returns normalized href attributes 8586 div.innerHTML = "<a href='#'></a>"; 8587 8588 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8589 div.firstChild.getAttribute("href") !== "#" ) { 8590 8591 Expr.attrHandle.href = function( elem ) { 8592 return elem.getAttribute( "href", 2 ); 8593 }; 8594 } 8595 8596 // release memory in IE 8597 div = null; 8598 })(); 8599 8600 if ( document.querySelectorAll ) { 8601 (function(){ 8602 var oldSizzle = Sizzle, 8603 div = document.createElement("div"), 8604 id = "__sizzle__"; 8605 8606 div.innerHTML = "<p class='TEST'></p>"; 8607 8608 // Safari can't handle uppercase or unicode characters when 8609 // in quirks mode. 8610 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8611 return; 8612 } 8613 8614 Sizzle = function( query, context, extra, seed ) { 8615 context = context || document; 8616 8617 // Only use querySelectorAll on non-XML documents 8618 // (ID selectors don't work in non-HTML documents) 8619 if ( !seed && !Sizzle.isXML(context) ) { 8620 // See if we find a selector to speed up 8621 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8622 8623 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8624 // Speed-up: Sizzle("TAG") 8625 if ( match[1] ) { 8626 return makeArray( context.getElementsByTagName( query ), extra ); 8627 8628 // Speed-up: Sizzle(".CLASS") 8629 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8630 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8631 } 8632 } 8633 8634 if ( context.nodeType === 9 ) { 8635 // Speed-up: Sizzle("body") 8636 // The body element only exists once, optimize finding it 8637 if ( query === "body" && context.body ) { 8638 return makeArray( [ context.body ], extra ); 8639 8640 // Speed-up: Sizzle("#ID") 8641 } else if ( match && match[3] ) { 8642 var elem = context.getElementById( match[3] ); 8643 8644 // Check parentNode to catch when Blackberry 4.6 returns 8645 // nodes that are no longer in the document #6963 8646 if ( elem && elem.parentNode ) { 8647 // Handle the case where IE and Opera return items 8648 // by name instead of ID 8649 if ( elem.id === match[3] ) { 8650 return makeArray( [ elem ], extra ); 8651 } 8652 8653 } else { 8654 return makeArray( [], extra ); 8655 } 8656 } 8657 8658 try { 8659 return makeArray( context.querySelectorAll(query), extra ); 8660 } catch(qsaError) {} 8661 8662 // qSA works strangely on Element-rooted queries 8663 // We can work around this by specifying an extra ID on the root 8664 // and working up from there (Thanks to Andrew Dupont for the technique) 8665 // IE 8 doesn't work on object elements 8666 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8667 var oldContext = context, 8668 old = context.getAttribute( "id" ), 8669 nid = old || id, 8670 hasParent = context.parentNode, 8671 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8672 8673 if ( !old ) { 8674 context.setAttribute( "id", nid ); 8675 } else { 8676 nid = nid.replace( /'/g, "\\$&" ); 8677 } 8678 if ( relativeHierarchySelector && hasParent ) { 8679 context = context.parentNode; 8680 } 8681 8682 try { 8683 if ( !relativeHierarchySelector || hasParent ) { 8684 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8685 } 8686 8687 } catch(pseudoError) { 8688 } finally { 8689 if ( !old ) { 8690 oldContext.removeAttribute( "id" ); 8691 } 8692 } 8693 } 8694 } 8695 8696 return oldSizzle(query, context, extra, seed); 8697 }; 8698 8699 for ( var prop in oldSizzle ) { 8700 Sizzle[ prop ] = oldSizzle[ prop ]; 8701 } 8702 8703 // release memory in IE 8704 div = null; 8705 })(); 8706 } 8707 8708 (function(){ 8709 var html = document.documentElement, 8710 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 8711 8712 if ( matches ) { 8713 // Check to see if it's possible to do matchesSelector 8714 // on a disconnected node (IE 9 fails this) 8715 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 8716 pseudoWorks = false; 8717 8718 try { 8719 // This should fail with an exception 8720 // Gecko does not error, returns false instead 8721 matches.call( document.documentElement, "[test!='']:sizzle" ); 8722 8723 } catch( pseudoError ) { 8724 pseudoWorks = true; 8725 } 8726 8727 Sizzle.matchesSelector = function( node, expr ) { 8728 // Make sure that attribute selectors are quoted 8729 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 8730 8731 if ( !Sizzle.isXML( node ) ) { 8732 try { 8733 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 8734 var ret = matches.call( node, expr ); 8735 8736 // IE 9's matchesSelector returns false on disconnected nodes 8737 if ( ret || !disconnectedMatch || 8738 // As well, disconnected nodes are said to be in a document 8739 // fragment in IE 9, so check for that 8740 node.document && node.document.nodeType !== 11 ) { 8741 return ret; 8742 } 8743 } 8744 } catch(e) {} 8745 } 8746 8747 return Sizzle(expr, null, null, [node]).length > 0; 8748 }; 8749 } 8750 })(); 8751 8752 (function(){ 8753 var div = document.createElement("div"); 8754 8755 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 8756 8757 // Opera can't find a second classname (in 9.6) 8758 // Also, make sure that getElementsByClassName actually exists 8759 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 8760 return; 8761 } 8762 8763 // Safari caches class attributes, doesn't catch changes (in 3.2) 8764 div.lastChild.className = "e"; 8765 8766 if ( div.getElementsByClassName("e").length === 1 ) { 8767 return; 8768 } 8769 8770 Expr.order.splice(1, 0, "CLASS"); 8771 Expr.find.CLASS = function( match, context, isXML ) { 8772 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 8773 return context.getElementsByClassName(match[1]); 8774 } 8775 }; 8776 8777 // release memory in IE 8778 div = null; 8779 })(); 8780 8781 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8782 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8783 var elem = checkSet[i]; 8784 8785 if ( elem ) { 8786 var match = false; 8787 8788 elem = elem[dir]; 8789 8790 while ( elem ) { 8791 if ( elem[ expando ] === doneName ) { 8792 match = checkSet[elem.sizset]; 8793 break; 8794 } 8795 8796 if ( elem.nodeType === 1 && !isXML ){ 8797 elem[ expando ] = doneName; 8798 elem.sizset = i; 8799 } 8800 8801 if ( elem.nodeName.toLowerCase() === cur ) { 8802 match = elem; 8803 break; 8804 } 8805 8806 elem = elem[dir]; 8807 } 8808 8809 checkSet[i] = match; 8810 } 8811 } 8812 } 8813 8814 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8815 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8816 var elem = checkSet[i]; 8817 8818 if ( elem ) { 8819 var match = false; 8820 8821 elem = elem[dir]; 8822 8823 while ( elem ) { 8824 if ( elem[ expando ] === doneName ) { 8825 match = checkSet[elem.sizset]; 8826 break; 8827 } 8828 8829 if ( elem.nodeType === 1 ) { 8830 if ( !isXML ) { 8831 elem[ expando ] = doneName; 8832 elem.sizset = i; 8833 } 8834 8835 if ( typeof cur !== "string" ) { 8836 if ( elem === cur ) { 8837 match = true; 8838 break; 8839 } 8840 8841 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 8842 match = elem; 8843 break; 8844 } 8845 } 8846 8847 elem = elem[dir]; 8848 } 8849 8850 checkSet[i] = match; 8851 } 8852 } 8853 } 8854 8855 if ( document.documentElement.contains ) { 8856 Sizzle.contains = function( a, b ) { 8857 return a !== b && (a.contains ? a.contains(b) : true); 8858 }; 8859 8860 } else if ( document.documentElement.compareDocumentPosition ) { 8861 Sizzle.contains = function( a, b ) { 8862 return !!(a.compareDocumentPosition(b) & 16); 8863 }; 8864 8865 } else { 8866 Sizzle.contains = function() { 8867 return false; 8868 }; 8869 } 8870 8871 Sizzle.isXML = function( elem ) { 8872 // documentElement is verified for cases where it doesn't yet exist 8873 // (such as loading iframes in IE - #4833) 8874 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 8875 8876 return documentElement ? documentElement.nodeName !== "HTML" : false; 8877 }; 8878 8879 var posProcess = function( selector, context, seed ) { 8880 var match, 8881 tmpSet = [], 8882 later = "", 8883 root = context.nodeType ? [context] : context; 8884 8885 // Position selectors must be done after the filter 8886 // And so must :not(positional) so we move all PSEUDOs to the end 8887 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 8888 later += match[0]; 8889 selector = selector.replace( Expr.match.PSEUDO, "" ); 8890 } 8891 8892 selector = Expr.relative[selector] ? selector + "*" : selector; 8893 8894 for ( var i = 0, l = root.length; i < l; i++ ) { 8895 Sizzle( selector, root[i], tmpSet, seed ); 8896 } 8897 8898 return Sizzle.filter( later, tmpSet ); 8899 }; 8900 8901 // EXPOSE 8902 8903 window.tinymce.dom.Sizzle = Sizzle; 8904 8905 })(); 8906 8907 8908 (function(tinymce) { 8909 tinymce.dom.Element = function(id, settings) { 8910 var t = this, dom, el; 8911 8912 t.settings = settings = settings || {}; 8913 t.id = id; 8914 t.dom = dom = settings.dom || tinymce.DOM; 8915 8916 // Only IE leaks DOM references, this is a lot faster 8917 if (!tinymce.isIE) 8918 el = dom.get(t.id); 8919 8920 tinymce.each( 8921 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 8922 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 8923 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 8924 'isHidden,setHTML,get').split(/,/), function(k) { 8925 t[k] = function() { 8926 var a = [id], i; 8927 8928 for (i = 0; i < arguments.length; i++) 8929 a.push(arguments[i]); 8930 8931 a = dom[k].apply(dom, a); 8932 t.update(k); 8933 8934 return a; 8935 }; 8936 } 8937 ); 8938 8939 tinymce.extend(t, { 8940 on : function(n, f, s) { 8941 return tinymce.dom.Event.add(t.id, n, f, s); 8942 }, 8943 8944 getXY : function() { 8945 return { 8946 x : parseInt(t.getStyle('left')), 8947 y : parseInt(t.getStyle('top')) 8948 }; 8949 }, 8950 8951 getSize : function() { 8952 var n = dom.get(t.id); 8953 8954 return { 8955 w : parseInt(t.getStyle('width') || n.clientWidth), 8956 h : parseInt(t.getStyle('height') || n.clientHeight) 8957 }; 8958 }, 8959 8960 moveTo : function(x, y) { 8961 t.setStyles({left : x, top : y}); 8962 }, 8963 8964 moveBy : function(x, y) { 8965 var p = t.getXY(); 8966 8967 t.moveTo(p.x + x, p.y + y); 8968 }, 8969 8970 resizeTo : function(w, h) { 8971 t.setStyles({width : w, height : h}); 8972 }, 8973 8974 resizeBy : function(w, h) { 8975 var s = t.getSize(); 8976 8977 t.resizeTo(s.w + w, s.h + h); 8978 }, 8979 8980 update : function(k) { 8981 var b; 8982 8983 if (tinymce.isIE6 && settings.blocker) { 8984 k = k || ''; 8985 8986 // Ignore getters 8987 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 8988 return; 8989 8990 // Remove blocker on remove 8991 if (k == 'remove') { 8992 dom.remove(t.blocker); 8993 return; 8994 } 8995 8996 if (!t.blocker) { 8997 t.blocker = dom.uniqueId(); 8998 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 8999 dom.setStyle(b, 'opacity', 0); 9000 } else 9001 b = dom.get(t.blocker); 9002 9003 dom.setStyles(b, { 9004 left : t.getStyle('left', 1), 9005 top : t.getStyle('top', 1), 9006 width : t.getStyle('width', 1), 9007 height : t.getStyle('height', 1), 9008 display : t.getStyle('display', 1), 9009 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9010 }); 9011 } 9012 } 9013 }); 9014 }; 9015 })(tinymce); 9016 9017 (function(tinymce) { 9018 function trimNl(s) { 9019 return s.replace(/[\n\r]+/g, ''); 9020 }; 9021 9022 // Shorten names 9023 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9024 9025 tinymce.create('tinymce.dom.Selection', { 9026 Selection : function(dom, win, serializer, editor) { 9027 var t = this; 9028 9029 t.dom = dom; 9030 t.win = win; 9031 t.serializer = serializer; 9032 t.editor = editor; 9033 9034 // Add events 9035 each([ 9036 'onBeforeSetContent', 9037 9038 'onBeforeGetContent', 9039 9040 'onSetContent', 9041 9042 'onGetContent' 9043 ], function(e) { 9044 t[e] = new tinymce.util.Dispatcher(t); 9045 }); 9046 9047 // No W3C Range support 9048 if (!t.win.getSelection) 9049 t.tridentSel = new tinymce.dom.TridentSelection(t); 9050 9051 if (tinymce.isIE && dom.boxModel) 9052 this._fixIESelection(); 9053 9054 // Prevent leaks 9055 tinymce.addUnload(t.destroy, t); 9056 }, 9057 9058 setCursorLocation: function(node, offset) { 9059 var t = this; var r = t.dom.createRng(); 9060 r.setStart(node, offset); 9061 r.setEnd(node, offset); 9062 t.setRng(r); 9063 t.collapse(false); 9064 }, 9065 getContent : function(s) { 9066 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9067 9068 s = s || {}; 9069 wb = wa = ''; 9070 s.get = true; 9071 s.format = s.format || 'html'; 9072 s.forced_root_block = ''; 9073 t.onBeforeGetContent.dispatch(t, s); 9074 9075 if (s.format == 'text') 9076 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9077 9078 if (r.cloneContents) { 9079 n = r.cloneContents(); 9080 9081 if (n) 9082 e.appendChild(n); 9083 } else if (is(r.item) || is(r.htmlText)) { 9084 // IE will produce invalid markup if elements are present that 9085 // it doesn't understand like custom elements or HTML5 elements. 9086 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9087 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9088 e.removeChild(e.firstChild); 9089 } else 9090 e.innerHTML = r.toString(); 9091 9092 // Keep whitespace before and after 9093 if (/^\s/.test(e.innerHTML)) 9094 wb = ' '; 9095 9096 if (/\s+$/.test(e.innerHTML)) 9097 wa = ' '; 9098 9099 s.getInner = true; 9100 9101 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9102 t.onGetContent.dispatch(t, s); 9103 9104 return s.content; 9105 }, 9106 9107 setContent : function(content, args) { 9108 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9109 9110 args = args || {format : 'html'}; 9111 args.set = true; 9112 content = args.content = content; 9113 9114 // Dispatch before set content event 9115 if (!args.no_events) 9116 self.onBeforeSetContent.dispatch(self, args); 9117 9118 content = args.content; 9119 9120 if (rng.insertNode) { 9121 // Make caret marker since insertNode places the caret in the beginning of text after insert 9122 content += '<span id="__caret">_</span>'; 9123 9124 // Delete and insert new node 9125 if (rng.startContainer == doc && rng.endContainer == doc) { 9126 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9127 doc.body.innerHTML = content; 9128 } else { 9129 rng.deleteContents(); 9130 9131 if (doc.body.childNodes.length === 0) { 9132 doc.body.innerHTML = content; 9133 } else { 9134 // createContextualFragment doesn't exists in IE 9 DOMRanges 9135 if (rng.createContextualFragment) { 9136 rng.insertNode(rng.createContextualFragment(content)); 9137 } else { 9138 // Fake createContextualFragment call in IE 9 9139 frag = doc.createDocumentFragment(); 9140 temp = doc.createElement('div'); 9141 9142 frag.appendChild(temp); 9143 temp.outerHTML = content; 9144 9145 rng.insertNode(frag); 9146 } 9147 } 9148 } 9149 9150 // Move to caret marker 9151 caretNode = self.dom.get('__caret'); 9152 9153 // Make sure we wrap it compleatly, Opera fails with a simple select call 9154 rng = doc.createRange(); 9155 rng.setStartBefore(caretNode); 9156 rng.setEndBefore(caretNode); 9157 self.setRng(rng); 9158 9159 // Remove the caret position 9160 self.dom.remove('__caret'); 9161 9162 try { 9163 self.setRng(rng); 9164 } catch (ex) { 9165 // Might fail on Opera for some odd reason 9166 } 9167 } else { 9168 if (rng.item) { 9169 // Delete content and get caret text selection 9170 doc.execCommand('Delete', false, null); 9171 rng = self.getRng(); 9172 } 9173 9174 // Explorer removes spaces from the beginning of pasted contents 9175 if (/^\s+/.test(content)) { 9176 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9177 self.dom.remove('__mce_tmp'); 9178 } else 9179 rng.pasteHTML(content); 9180 } 9181 9182 // Dispatch set content event 9183 if (!args.no_events) 9184 self.onSetContent.dispatch(self, args); 9185 }, 9186 9187 getStart : function() { 9188 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9189 9190 if (rng.duplicate || rng.item) { 9191 // Control selection, return first item 9192 if (rng.item) 9193 return rng.item(0); 9194 9195 // Get start element 9196 checkRng = rng.duplicate(); 9197 checkRng.collapse(1); 9198 startElement = checkRng.parentElement(); 9199 if (startElement.ownerDocument !== self.dom.doc) { 9200 startElement = self.dom.getRoot(); 9201 } 9202 9203 // Check if range parent is inside the start element, then return the inner parent element 9204 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9205 parentElement = node = rng.parentElement(); 9206 while (node = node.parentNode) { 9207 if (node == startElement) { 9208 startElement = parentElement; 9209 break; 9210 } 9211 } 9212 9213 return startElement; 9214 } else { 9215 startElement = rng.startContainer; 9216 9217 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9218 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9219 9220 if (startElement && startElement.nodeType == 3) 9221 return startElement.parentNode; 9222 9223 return startElement; 9224 } 9225 }, 9226 9227 getEnd : function() { 9228 var self = this, rng = self.getRng(), endElement, endOffset; 9229 9230 if (rng.duplicate || rng.item) { 9231 if (rng.item) 9232 return rng.item(0); 9233 9234 rng = rng.duplicate(); 9235 rng.collapse(0); 9236 endElement = rng.parentElement(); 9237 if (endElement.ownerDocument !== self.dom.doc) { 9238 endElement = self.dom.getRoot(); 9239 } 9240 9241 if (endElement && endElement.nodeName == 'BODY') 9242 return endElement.lastChild || endElement; 9243 9244 return endElement; 9245 } else { 9246 endElement = rng.endContainer; 9247 endOffset = rng.endOffset; 9248 9249 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9250 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9251 9252 if (endElement && endElement.nodeType == 3) 9253 return endElement.parentNode; 9254 9255 return endElement; 9256 } 9257 }, 9258 9259 getBookmark : function(type, normalized) { 9260 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9261 9262 function findIndex(name, element) { 9263 var index = 0; 9264 9265 each(dom.select(name), function(node, i) { 9266 if (node == element) 9267 index = i; 9268 }); 9269 9270 return index; 9271 }; 9272 9273 function normalizeTableCellSelection(rng) { 9274 function moveEndPoint(start) { 9275 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9276 9277 container = rng[prefix + 'Container']; 9278 offset = rng[prefix + 'Offset']; 9279 9280 if (container.nodeType == 1 && container.nodeName == "TR") { 9281 childNodes = container.childNodes; 9282 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9283 if (container) { 9284 offset = start ? 0 : container.childNodes.length; 9285 rng['set' + (start ? 'Start' : 'End')](container, offset); 9286 } 9287 } 9288 }; 9289 9290 moveEndPoint(true); 9291 moveEndPoint(); 9292 9293 return rng; 9294 }; 9295 9296 function getLocation() { 9297 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9298 9299 function getPoint(rng, start) { 9300 var container = rng[start ? 'startContainer' : 'endContainer'], 9301 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9302 9303 if (container.nodeType == 3) { 9304 if (normalized) { 9305 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9306 offset += node.nodeValue.length; 9307 } 9308 9309 point.push(offset); 9310 } else { 9311 childNodes = container.childNodes; 9312 9313 if (offset >= childNodes.length && childNodes.length) { 9314 after = 1; 9315 offset = Math.max(0, childNodes.length - 1); 9316 } 9317 9318 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9319 } 9320 9321 for (; container && container != root; container = container.parentNode) 9322 point.push(t.dom.nodeIndex(container, normalized)); 9323 9324 return point; 9325 }; 9326 9327 bookmark.start = getPoint(rng, true); 9328 9329 if (!t.isCollapsed()) 9330 bookmark.end = getPoint(rng); 9331 9332 return bookmark; 9333 }; 9334 9335 if (type == 2) { 9336 if (t.tridentSel) 9337 return t.tridentSel.getBookmark(type); 9338 9339 return getLocation(); 9340 } 9341 9342 // Handle simple range 9343 if (type) 9344 return {rng : t.getRng()}; 9345 9346 rng = t.getRng(); 9347 id = dom.uniqueId(); 9348 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9349 styles = 'overflow:hidden;line-height:0px'; 9350 9351 // Explorer method 9352 if (rng.duplicate || rng.item) { 9353 // Text selection 9354 if (!rng.item) { 9355 rng2 = rng.duplicate(); 9356 9357 try { 9358 // Insert start marker 9359 rng.collapse(); 9360 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9361 9362 // Insert end marker 9363 if (!collapsed) { 9364 rng2.collapse(false); 9365 9366 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9367 rng.moveToElementText(rng2.parentElement()); 9368 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9369 rng2.move('character', -1); 9370 9371 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9372 } 9373 } catch (ex) { 9374 // IE might throw unspecified error so lets ignore it 9375 return null; 9376 } 9377 } else { 9378 // Control selection 9379 element = rng.item(0); 9380 name = element.nodeName; 9381 9382 return {name : name, index : findIndex(name, element)}; 9383 } 9384 } else { 9385 element = t.getNode(); 9386 name = element.nodeName; 9387 if (name == 'IMG') 9388 return {name : name, index : findIndex(name, element)}; 9389 9390 // W3C method 9391 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9392 9393 // Insert end marker 9394 if (!collapsed) { 9395 rng2.collapse(false); 9396 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9397 } 9398 9399 rng = normalizeTableCellSelection(rng); 9400 rng.collapse(true); 9401 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9402 } 9403 9404 t.moveToBookmark({id : id, keep : 1}); 9405 9406 return {id : id}; 9407 }, 9408 9409 moveToBookmark : function(bookmark) { 9410 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; 9411 9412 function setEndPoint(start) { 9413 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9414 9415 if (point) { 9416 offset = point[0]; 9417 9418 // Find container node 9419 for (node = root, i = point.length - 1; i >= 1; i--) { 9420 children = node.childNodes; 9421 9422 if (point[i] > children.length - 1) 9423 return; 9424 9425 node = children[point[i]]; 9426 } 9427 9428 // Move text offset to best suitable location 9429 if (node.nodeType === 3) 9430 offset = Math.min(point[0], node.nodeValue.length); 9431 9432 // Move element offset to best suitable location 9433 if (node.nodeType === 1) 9434 offset = Math.min(point[0], node.childNodes.length); 9435 9436 // Set offset within container node 9437 if (start) 9438 rng.setStart(node, offset); 9439 else 9440 rng.setEnd(node, offset); 9441 } 9442 9443 return true; 9444 }; 9445 9446 function restoreEndPoint(suffix) { 9447 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9448 9449 if (marker) { 9450 node = marker.parentNode; 9451 9452 if (suffix == 'start') { 9453 if (!keep) { 9454 idx = dom.nodeIndex(marker); 9455 } else { 9456 node = marker.firstChild; 9457 idx = 1; 9458 } 9459 9460 startContainer = endContainer = node; 9461 startOffset = endOffset = idx; 9462 } else { 9463 if (!keep) { 9464 idx = dom.nodeIndex(marker); 9465 } else { 9466 node = marker.firstChild; 9467 idx = 1; 9468 } 9469 9470 endContainer = node; 9471 endOffset = idx; 9472 } 9473 9474 if (!keep) { 9475 prev = marker.previousSibling; 9476 next = marker.nextSibling; 9477 9478 // Remove all marker text nodes 9479 each(tinymce.grep(marker.childNodes), function(node) { 9480 if (node.nodeType == 3) 9481 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9482 }); 9483 9484 // Remove marker but keep children if for example contents where inserted into the marker 9485 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9486 while (marker = dom.get(bookmark.id + '_' + suffix)) 9487 dom.remove(marker, 1); 9488 9489 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9490 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact 9491 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9492 idx = prev.nodeValue.length; 9493 prev.appendData(next.nodeValue); 9494 dom.remove(next); 9495 9496 if (suffix == 'start') { 9497 startContainer = endContainer = prev; 9498 startOffset = endOffset = idx; 9499 } else { 9500 endContainer = prev; 9501 endOffset = idx; 9502 } 9503 } 9504 } 9505 } 9506 }; 9507 9508 function addBogus(node) { 9509 // Adds a bogus BR element for empty block elements 9510 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9511 node.innerHTML = '<br data-mce-bogus="1" />'; 9512 9513 return node; 9514 }; 9515 9516 if (bookmark) { 9517 if (bookmark.start) { 9518 rng = dom.createRng(); 9519 root = dom.getRoot(); 9520 9521 if (t.tridentSel) 9522 return t.tridentSel.moveToBookmark(bookmark); 9523 9524 if (setEndPoint(true) && setEndPoint()) { 9525 t.setRng(rng); 9526 } 9527 } else if (bookmark.id) { 9528 // Restore start/end points 9529 restoreEndPoint('start'); 9530 restoreEndPoint('end'); 9531 9532 if (startContainer) { 9533 rng = dom.createRng(); 9534 rng.setStart(addBogus(startContainer), startOffset); 9535 rng.setEnd(addBogus(endContainer), endOffset); 9536 t.setRng(rng); 9537 } 9538 } else if (bookmark.name) { 9539 t.select(dom.select(bookmark.name)[bookmark.index]); 9540 } else if (bookmark.rng) 9541 t.setRng(bookmark.rng); 9542 } 9543 }, 9544 9545 select : function(node, content) { 9546 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9547 9548 function setPoint(node, start) { 9549 var walker = new TreeWalker(node, node); 9550 9551 do { 9552 // Text node 9553 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9554 if (start) 9555 rng.setStart(node, 0); 9556 else 9557 rng.setEnd(node, node.nodeValue.length); 9558 9559 return; 9560 } 9561 9562 // BR element 9563 if (node.nodeName == 'BR') { 9564 if (start) 9565 rng.setStartBefore(node); 9566 else 9567 rng.setEndBefore(node); 9568 9569 return; 9570 } 9571 } while (node = (start ? walker.next() : walker.prev())); 9572 }; 9573 9574 if (node) { 9575 idx = dom.nodeIndex(node); 9576 rng.setStart(node.parentNode, idx); 9577 rng.setEnd(node.parentNode, idx + 1); 9578 9579 // Find first/last text node or BR element 9580 if (content) { 9581 setPoint(node, 1); 9582 setPoint(node); 9583 } 9584 9585 t.setRng(rng); 9586 } 9587 9588 return node; 9589 }, 9590 9591 isCollapsed : function() { 9592 var t = this, r = t.getRng(), s = t.getSel(); 9593 9594 if (!r || r.item) 9595 return false; 9596 9597 if (r.compareEndPoints) 9598 return r.compareEndPoints('StartToEnd', r) === 0; 9599 9600 return !s || r.collapsed; 9601 }, 9602 9603 collapse : function(to_start) { 9604 var self = this, rng = self.getRng(), node; 9605 9606 // Control range on IE 9607 if (rng.item) { 9608 node = rng.item(0); 9609 rng = self.win.document.body.createTextRange(); 9610 rng.moveToElementText(node); 9611 } 9612 9613 rng.collapse(!!to_start); 9614 self.setRng(rng); 9615 }, 9616 9617 getSel : function() { 9618 var t = this, w = this.win; 9619 9620 return w.getSelection ? w.getSelection() : w.document.selection; 9621 }, 9622 9623 getRng : function(w3c) { 9624 var self = this, selection, rng, elm, doc = self.win.document; 9625 9626 // Found tridentSel object then we need to use that one 9627 if (w3c && self.tridentSel) { 9628 return self.tridentSel.getRangeAt(0); 9629 } 9630 9631 try { 9632 if (selection = self.getSel()) { 9633 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9634 } 9635 } catch (ex) { 9636 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9637 } 9638 9639 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9640 if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { 9641 elm = doc.selection.createRange().item(0); 9642 rng = doc.createRange(); 9643 rng.setStartBefore(elm); 9644 rng.setEndAfter(elm); 9645 } 9646 9647 // No range found then create an empty one 9648 // This can occur when the editor is placed in a hidden container element on Gecko 9649 // Or on IE when there was an exception 9650 if (!rng) { 9651 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9652 } 9653 9654 // If range is at start of document then move it to start of body 9655 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9656 elm = self.dom.getRoot(); 9657 rng.setStart(elm, 0); 9658 rng.setEnd(elm, 0); 9659 } 9660 9661 if (self.selectedRange && self.explicitRange) { 9662 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9663 // Safari, Opera and Chrome only ever select text which causes the range to change. 9664 // This lets us use the originally set range if the selection hasn't been changed by the user. 9665 rng = self.explicitRange; 9666 } else { 9667 self.selectedRange = null; 9668 self.explicitRange = null; 9669 } 9670 } 9671 9672 return rng; 9673 }, 9674 9675 setRng : function(r, forward) { 9676 var s, t = this; 9677 9678 if (!t.tridentSel) { 9679 s = t.getSel(); 9680 9681 if (s) { 9682 t.explicitRange = r; 9683 9684 try { 9685 s.removeAllRanges(); 9686 } catch (ex) { 9687 // IE9 might throw errors here don't know why 9688 } 9689 9690 s.addRange(r); 9691 9692 // Forward is set to false and we have an extend function 9693 if (forward === false && s.extend) { 9694 s.collapse(r.endContainer, r.endOffset); 9695 s.extend(r.startContainer, r.startOffset); 9696 } 9697 9698 // adding range isn't always successful so we need to check range count otherwise an exception can occur 9699 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 9700 } 9701 } else { 9702 // Is W3C Range 9703 if (r.cloneRange) { 9704 try { 9705 t.tridentSel.addRange(r); 9706 return; 9707 } catch (ex) { 9708 //IE9 throws an error here if called before selection is placed in the editor 9709 } 9710 } 9711 9712 // Is IE specific range 9713 try { 9714 r.select(); 9715 } catch (ex) { 9716 // Needed for some odd IE bug #1843306 9717 } 9718 } 9719 }, 9720 9721 setNode : function(n) { 9722 var t = this; 9723 9724 t.setContent(t.dom.getOuterHTML(n)); 9725 9726 return n; 9727 }, 9728 9729 getNode : function() { 9730 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 9731 9732 function skipEmptyTextNodes(n, forwards) { 9733 var orig = n; 9734 while (n && n.nodeType === 3 && n.length === 0) { 9735 n = forwards ? n.nextSibling : n.previousSibling; 9736 } 9737 return n || orig; 9738 }; 9739 9740 // Range maybe lost after the editor is made visible again 9741 if (!rng) 9742 return t.dom.getRoot(); 9743 9744 if (rng.setStart) { 9745 elm = rng.commonAncestorContainer; 9746 9747 // Handle selection a image or other control like element such as anchors 9748 if (!rng.collapsed) { 9749 if (rng.startContainer == rng.endContainer) { 9750 if (rng.endOffset - rng.startOffset < 2) { 9751 if (rng.startContainer.hasChildNodes()) 9752 elm = rng.startContainer.childNodes[rng.startOffset]; 9753 } 9754 } 9755 9756 // If the anchor node is a element instead of a text node then return this element 9757 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 9758 // return sel.anchorNode.childNodes[sel.anchorOffset]; 9759 9760 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 9761 // This happens when you double click an underlined word in FireFox. 9762 if (start.nodeType === 3 && end.nodeType === 3) { 9763 if (start.length === rng.startOffset) { 9764 start = skipEmptyTextNodes(start.nextSibling, true); 9765 } else { 9766 start = start.parentNode; 9767 } 9768 if (rng.endOffset === 0) { 9769 end = skipEmptyTextNodes(end.previousSibling, false); 9770 } else { 9771 end = end.parentNode; 9772 } 9773 9774 if (start && start === end) 9775 return start; 9776 } 9777 } 9778 9779 if (elm && elm.nodeType == 3) 9780 return elm.parentNode; 9781 9782 return elm; 9783 } 9784 9785 return rng.item ? rng.item(0) : rng.parentElement(); 9786 }, 9787 9788 getSelectedBlocks : function(st, en) { 9789 var t = this, dom = t.dom, sb, eb, n, bl = []; 9790 9791 sb = dom.getParent(st || t.getStart(), dom.isBlock); 9792 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 9793 9794 if (sb) 9795 bl.push(sb); 9796 9797 if (sb && eb && sb != eb) { 9798 n = sb; 9799 9800 var walker = new TreeWalker(sb, dom.getRoot()); 9801 while ((n = walker.next()) && n != eb) { 9802 if (dom.isBlock(n)) 9803 bl.push(n); 9804 } 9805 } 9806 9807 if (eb && sb != eb) 9808 bl.push(eb); 9809 9810 return bl; 9811 }, 9812 9813 isForward: function(){ 9814 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 9815 9816 // No support for selection direction then always return true 9817 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 9818 return true; 9819 } 9820 9821 anchorRange = dom.createRng(); 9822 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 9823 anchorRange.collapse(true); 9824 9825 focusRange = dom.createRng(); 9826 focusRange.setStart(sel.focusNode, sel.focusOffset); 9827 focusRange.collapse(true); 9828 9829 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 9830 }, 9831 9832 normalize : function() { 9833 var self = this, rng, normalized, collapsed, node, sibling; 9834 9835 function normalizeEndPoint(start) { 9836 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 9837 9838 function hasBrBeforeAfter(node, left) { 9839 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 9840 9841 while (node = walker[left ? 'prev' : 'next']()) { 9842 if (node.nodeName === "BR") { 9843 return true; 9844 } 9845 } 9846 }; 9847 9848 // Walks the dom left/right to find a suitable text node to move the endpoint into 9849 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 9850 function findTextNodeRelative(left, startNode) { 9851 var walker, lastInlineElement; 9852 9853 startNode = startNode || container; 9854 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 9855 9856 // Walk left until we hit a text node we can move to or a block/br/img 9857 while (node = walker[left ? 'prev' : 'next']()) { 9858 // Found text node that has a length 9859 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9860 container = node; 9861 offset = left ? node.nodeValue.length : 0; 9862 normalized = true; 9863 return; 9864 } 9865 9866 // Break if we find a block or a BR/IMG/INPUT etc 9867 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9868 return; 9869 } 9870 9871 lastInlineElement = node; 9872 } 9873 9874 // Only fetch the last inline element when in caret mode for now 9875 if (collapsed && lastInlineElement) { 9876 container = lastInlineElement; 9877 normalized = true; 9878 offset = 0; 9879 } 9880 }; 9881 9882 container = rng[(start ? 'start' : 'end') + 'Container']; 9883 offset = rng[(start ? 'start' : 'end') + 'Offset']; 9884 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 9885 9886 // If the container is a document move it to the body element 9887 if (container.nodeType === 9) { 9888 container = dom.getRoot(); 9889 offset = 0; 9890 } 9891 9892 // If the container is body try move it into the closest text node or position 9893 if (container === body) { 9894 // If start is before/after a image, table etc 9895 if (start) { 9896 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 9897 if (node) { 9898 nodeName = node.nodeName.toLowerCase(); 9899 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 9900 return; 9901 } 9902 } 9903 } 9904 9905 // Resolve the index 9906 if (container.hasChildNodes()) { 9907 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 9908 offset = 0; 9909 9910 // Don't walk into elements that doesn't have any child nodes like a IMG 9911 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 9912 // Walk the DOM to find a text node to place the caret at or a BR 9913 node = container; 9914 walker = new TreeWalker(container, body); 9915 9916 do { 9917 // Found a text node use that position 9918 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9919 offset = start ? 0 : node.nodeValue.length; 9920 container = node; 9921 normalized = true; 9922 break; 9923 } 9924 9925 // Found a BR/IMG element that we can place the caret before 9926 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9927 offset = dom.nodeIndex(node); 9928 container = node.parentNode; 9929 9930 // Put caret after image when moving the end point 9931 if (node.nodeName == "IMG" && !start) { 9932 offset++; 9933 } 9934 9935 normalized = true; 9936 break; 9937 } 9938 } while (node = (start ? walker.next() : walker.prev())); 9939 } 9940 } 9941 } 9942 9943 // Lean the caret to the left if possible 9944 if (collapsed) { 9945 // So this: <b>x</b><i>|x</i> 9946 // Becomes: <b>x|</b><i>x</i> 9947 // Seems that only gecko has issues with this 9948 if (container.nodeType === 3 && offset === 0) { 9949 findTextNodeRelative(true); 9950 } 9951 9952 // Lean left into empty inline elements when the caret is before a BR 9953 // So this: <i><b></b><i>|<br></i> 9954 // Becomes: <i><b>|</b><i><br></i> 9955 // Seems that only gecko has issues with this 9956 if (container.nodeType === 1) { 9957 node = container.childNodes[offset]; 9958 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 9959 findTextNodeRelative(true, container.childNodes[offset]); 9960 } 9961 } 9962 } 9963 9964 // Lean the start of the selection right if possible 9965 // So this: x[<b>x]</b> 9966 // Becomes: x<b>[x]</b> 9967 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 9968 findTextNodeRelative(false); 9969 } 9970 9971 // Set endpoint if it was normalized 9972 if (normalized) 9973 rng['set' + (start ? 'Start' : 'End')](container, offset); 9974 }; 9975 9976 // Normalize only on non IE browsers for now 9977 if (tinymce.isIE) 9978 return; 9979 9980 rng = self.getRng(); 9981 collapsed = rng.collapsed; 9982 9983 // Normalize the end points 9984 normalizeEndPoint(true); 9985 9986 if (!collapsed) 9987 normalizeEndPoint(); 9988 9989 // Set the selection if it was normalized 9990 if (normalized) { 9991 // If it was collapsed then make sure it still is 9992 if (collapsed) { 9993 rng.collapse(true); 9994 } 9995 9996 //console.log(self.dom.dumpRng(rng)); 9997 self.setRng(rng, self.isForward()); 9998 } 9999 }, 10000 10001 selectorChanged: function(selector, callback) { 10002 var self = this, currentSelectors; 10003 10004 if (!self.selectorChangedData) { 10005 self.selectorChangedData = {}; 10006 currentSelectors = {}; 10007 10008 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10009 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10010 10011 // Check for new matching selectors 10012 each(self.selectorChangedData, function(callbacks, selector) { 10013 each(parents, function(node) { 10014 if (dom.is(node, selector)) { 10015 if (!currentSelectors[selector]) { 10016 // Execute callbacks 10017 each(callbacks, function(callback) { 10018 callback(true, {node: node, selector: selector, parents: parents}); 10019 }); 10020 10021 currentSelectors[selector] = callbacks; 10022 } 10023 10024 matchedSelectors[selector] = callbacks; 10025 return false; 10026 } 10027 }); 10028 }); 10029 10030 // Check if current selectors still match 10031 each(currentSelectors, function(callbacks, selector) { 10032 if (!matchedSelectors[selector]) { 10033 delete currentSelectors[selector]; 10034 10035 each(callbacks, function(callback) { 10036 callback(false, {node: node, selector: selector, parents: parents}); 10037 }); 10038 } 10039 }); 10040 }); 10041 } 10042 10043 // Add selector listeners 10044 if (!self.selectorChangedData[selector]) { 10045 self.selectorChangedData[selector] = []; 10046 } 10047 10048 self.selectorChangedData[selector].push(callback); 10049 10050 return self; 10051 }, 10052 10053 destroy : function(manual) { 10054 var self = this; 10055 10056 self.win = null; 10057 10058 // Manual destroy then remove unload handler 10059 if (!manual) 10060 tinymce.removeUnload(self.destroy); 10061 }, 10062 10063 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10064 _fixIESelection : function() { 10065 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10066 10067 // Return range from point or null if it failed 10068 function rngFromPoint(x, y) { 10069 var rng = body.createTextRange(); 10070 10071 try { 10072 rng.moveToPoint(x, y); 10073 } catch (ex) { 10074 // IE sometimes throws and exception, so lets just ignore it 10075 rng = null; 10076 } 10077 10078 return rng; 10079 }; 10080 10081 // Fires while the selection is changing 10082 function selectionChange(e) { 10083 var pointRng; 10084 10085 // Check if the button is down or not 10086 if (e.button) { 10087 // Create range from mouse position 10088 pointRng = rngFromPoint(e.x, e.y); 10089 10090 if (pointRng) { 10091 // Check if pointRange is before/after selection then change the endPoint 10092 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10093 pointRng.setEndPoint('StartToStart', startRng); 10094 else 10095 pointRng.setEndPoint('EndToEnd', startRng); 10096 10097 pointRng.select(); 10098 } 10099 } else 10100 endSelection(); 10101 } 10102 10103 // Removes listeners 10104 function endSelection() { 10105 var rng = doc.selection.createRange(); 10106 10107 // If the range is collapsed then use the last start range 10108 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10109 startRng.select(); 10110 10111 dom.unbind(doc, 'mouseup', endSelection); 10112 dom.unbind(doc, 'mousemove', selectionChange); 10113 startRng = started = 0; 10114 }; 10115 10116 // Make HTML element unselectable since we are going to handle selection by hand 10117 doc.documentElement.unselectable = true; 10118 10119 // Detect when user selects outside BODY 10120 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10121 if (e.target.nodeName === 'HTML') { 10122 if (started) 10123 endSelection(); 10124 10125 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10126 htmlElm = doc.documentElement; 10127 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10128 return; 10129 10130 started = 1; 10131 // Setup start position 10132 startRng = rngFromPoint(e.x, e.y); 10133 if (startRng) { 10134 // Listen for selection change events 10135 dom.bind(doc, 'mouseup', endSelection); 10136 dom.bind(doc, 'mousemove', selectionChange); 10137 10138 dom.win.focus(); 10139 startRng.select(); 10140 } 10141 } 10142 }); 10143 } 10144 }); 10145 })(tinymce); 10146 10147 (function(tinymce) { 10148 tinymce.dom.Serializer = function(settings, dom, schema) { 10149 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10150 10151 // Support the old apply_source_formatting option 10152 if (!settings.apply_source_formatting) 10153 settings.indent = false; 10154 10155 // Default DOM and Schema if they are undefined 10156 dom = dom || tinymce.DOM; 10157 schema = schema || new tinymce.html.Schema(settings); 10158 settings.entity_encoding = settings.entity_encoding || 'named'; 10159 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10160 10161 onPreProcess = new tinymce.util.Dispatcher(self); 10162 10163 onPostProcess = new tinymce.util.Dispatcher(self); 10164 10165 htmlParser = new tinymce.html.DomParser(settings, schema); 10166 10167 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10168 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10169 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10170 10171 while (i--) { 10172 node = nodes[i]; 10173 10174 value = node.attributes.map[internalName]; 10175 if (value !== undef) { 10176 // Set external name to internal value and remove internal 10177 node.attr(name, value.length > 0 ? value : null); 10178 node.attr(internalName, null); 10179 } else { 10180 // No internal attribute found then convert the value we have in the DOM 10181 value = node.attributes.map[name]; 10182 10183 if (name === "style") 10184 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10185 else if (urlConverter) 10186 value = urlConverter.call(urlConverterScope, value, name, node.name); 10187 10188 node.attr(name, value.length > 0 ? value : null); 10189 } 10190 } 10191 }); 10192 10193 // Remove internal classes mceItem<..> or mceSelected 10194 htmlParser.addAttributeFilter('class', function(nodes, name) { 10195 var i = nodes.length, node, value; 10196 10197 while (i--) { 10198 node = nodes[i]; 10199 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10200 node.attr('class', value.length > 0 ? value : null); 10201 } 10202 }); 10203 10204 // Remove bookmark elements 10205 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10206 var i = nodes.length, node; 10207 10208 while (i--) { 10209 node = nodes[i]; 10210 10211 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10212 node.remove(); 10213 } 10214 }); 10215 10216 // Remove expando attributes 10217 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10218 var i = nodes.length; 10219 10220 while (i--) { 10221 nodes[i].attr(name, null); 10222 } 10223 }); 10224 10225 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10226 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10227 var i = nodes.length, node, value; 10228 10229 function trim(value) { 10230 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10231 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10232 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10233 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10234 }; 10235 10236 while (i--) { 10237 node = nodes[i]; 10238 value = node.firstChild ? node.firstChild.value : ''; 10239 10240 if (name === "script") { 10241 // Remove mce- prefix from script elements 10242 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10243 10244 if (value.length > 0) 10245 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10246 } else { 10247 if (value.length > 0) 10248 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10249 } 10250 } 10251 }); 10252 10253 // Convert comments to cdata and handle protected comments 10254 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10255 var i = nodes.length, node; 10256 10257 while (i--) { 10258 node = nodes[i]; 10259 10260 if (node.value.indexOf('[CDATA[') === 0) { 10261 node.name = '#cdata'; 10262 node.type = 4; 10263 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10264 } else if (node.value.indexOf('mce:protected ') === 0) { 10265 node.name = "#text"; 10266 node.type = 3; 10267 node.raw = true; 10268 node.value = unescape(node.value).substr(14); 10269 } 10270 } 10271 }); 10272 10273 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10274 var i = nodes.length, node; 10275 10276 while (i--) { 10277 node = nodes[i]; 10278 if (node.type === 7) 10279 node.remove(); 10280 else if (node.type === 1) { 10281 if (name === "input" && !("type" in node.attributes.map)) 10282 node.attr('type', 'text'); 10283 } 10284 } 10285 }); 10286 10287 // Fix list elements, TODO: Replace this later 10288 if (settings.fix_list_elements) { 10289 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10290 var i = nodes.length, node, parentNode; 10291 10292 while (i--) { 10293 node = nodes[i]; 10294 parentNode = node.parent; 10295 10296 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10297 if (node.prev && node.prev.name === 'li') { 10298 node.prev.append(node); 10299 } 10300 } 10301 } 10302 }); 10303 } 10304 10305 // Remove internal data attributes 10306 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10307 var i = nodes.length; 10308 10309 while (i--) { 10310 nodes[i].attr(name, null); 10311 } 10312 }); 10313 10314 // Return public methods 10315 return { 10316 schema : schema, 10317 10318 addNodeFilter : htmlParser.addNodeFilter, 10319 10320 addAttributeFilter : htmlParser.addAttributeFilter, 10321 10322 onPreProcess : onPreProcess, 10323 10324 onPostProcess : onPostProcess, 10325 10326 serialize : function(node, args) { 10327 var impl, doc, oldDoc, htmlSerializer, content; 10328 10329 // Explorer won't clone contents of script and style and the 10330 // selected index of select elements are cleared on a clone operation. 10331 if (isIE && dom.select('script,style,select,map').length > 0) { 10332 content = node.innerHTML; 10333 node = node.cloneNode(false); 10334 dom.setHTML(node, content); 10335 } else 10336 node = node.cloneNode(true); 10337 10338 // Nodes needs to be attached to something in WebKit/Opera 10339 // Older builds of Opera crashes if you attach the node to an document created dynamically 10340 // and since we can't feature detect a crash we need to sniff the acutal build number 10341 // This fix will make DOM ranges and make Sizzle happy! 10342 impl = node.ownerDocument.implementation; 10343 if (impl.createHTMLDocument) { 10344 // Create an empty HTML document 10345 doc = impl.createHTMLDocument(""); 10346 10347 // Add the element or it's children if it's a body element to the new document 10348 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10349 doc.body.appendChild(doc.importNode(node, true)); 10350 }); 10351 10352 // Grab first child or body element for serialization 10353 if (node.nodeName != 'BODY') 10354 node = doc.body.firstChild; 10355 else 10356 node = doc.body; 10357 10358 // set the new document in DOMUtils so createElement etc works 10359 oldDoc = dom.doc; 10360 dom.doc = doc; 10361 } 10362 10363 args = args || {}; 10364 args.format = args.format || 'html'; 10365 10366 // Pre process 10367 if (!args.no_events) { 10368 args.node = node; 10369 onPreProcess.dispatch(self, args); 10370 } 10371 10372 // Setup serializer 10373 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10374 10375 // Parse and serialize HTML 10376 args.content = htmlSerializer.serialize( 10377 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10378 ); 10379 10380 // Replace all BOM characters for now until we can find a better solution 10381 if (!args.cleanup) 10382 args.content = args.content.replace(/\uFEFF|\u200B/g, ''); 10383 10384 // Post process 10385 if (!args.no_events) 10386 onPostProcess.dispatch(self, args); 10387 10388 // Restore the old document if it was changed 10389 if (oldDoc) 10390 dom.doc = oldDoc; 10391 10392 args.node = null; 10393 10394 return args.content; 10395 }, 10396 10397 addRules : function(rules) { 10398 schema.addValidElements(rules); 10399 }, 10400 10401 setRules : function(rules) { 10402 schema.setValidElements(rules); 10403 } 10404 }; 10405 }; 10406 })(tinymce); 10407 (function(tinymce) { 10408 tinymce.dom.ScriptLoader = function(settings) { 10409 var QUEUED = 0, 10410 LOADING = 1, 10411 LOADED = 2, 10412 states = {}, 10413 queue = [], 10414 scriptLoadedCallbacks = {}, 10415 queueLoadedCallbacks = [], 10416 loading = 0, 10417 undef; 10418 10419 function loadScript(url, callback) { 10420 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10421 10422 // Execute callback when script is loaded 10423 function done() { 10424 dom.remove(id); 10425 10426 if (elm) 10427 elm.onreadystatechange = elm.onload = elm = null; 10428 10429 callback(); 10430 }; 10431 10432 function error() { 10433 // Report the error so it's easier for people to spot loading errors 10434 if (typeof(console) !== "undefined" && console.log) 10435 console.log("Failed to load: " + url); 10436 10437 // We can't mark it as done if there is a load error since 10438 // A) We don't want to produce 404 errors on the server and 10439 // B) the onerror event won't fire on all browsers. 10440 // done(); 10441 }; 10442 10443 id = dom.uniqueId(); 10444 10445 if (tinymce.isIE6) { 10446 uri = new tinymce.util.URI(url); 10447 loc = location; 10448 10449 // If script is from same domain and we 10450 // use IE 6 then use XHR since it's more reliable 10451 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10452 tinymce.util.XHR.send({ 10453 url : tinymce._addVer(uri.getURI()), 10454 success : function(content) { 10455 // Create new temp script element 10456 var script = dom.create('script', { 10457 type : 'text/javascript' 10458 }); 10459 10460 // Evaluate script in global scope 10461 script.text = content; 10462 document.getElementsByTagName('head')[0].appendChild(script); 10463 dom.remove(script); 10464 10465 done(); 10466 }, 10467 10468 error : error 10469 }); 10470 10471 return; 10472 } 10473 } 10474 10475 // Create new script element 10476 elm = document.createElement('script'); 10477 elm.id = id; 10478 elm.type = 'text/javascript'; 10479 elm.src = tinymce._addVer(url); 10480 10481 // Add onload listener for non IE browsers since IE9 10482 // fires onload event before the script is parsed and executed 10483 if (!tinymce.isIE) 10484 elm.onload = done; 10485 10486 // Add onerror event will get fired on some browsers but not all of them 10487 elm.onerror = error; 10488 10489 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10490 if (!tinymce.isOpera) { 10491 elm.onreadystatechange = function() { 10492 var state = elm.readyState; 10493 10494 // Loaded state is passed on IE 6 however there 10495 // are known issues with this method but we can't use 10496 // XHR in a cross domain loading 10497 if (state == 'complete' || state == 'loaded') 10498 done(); 10499 }; 10500 } 10501 10502 // Most browsers support this feature so we report errors 10503 // for those at least to help users track their missing plugins etc 10504 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10505 /*elm.onerror = function() { 10506 alert('Failed to load: ' + url); 10507 };*/ 10508 10509 // Add script to document 10510 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10511 }; 10512 10513 this.isDone = function(url) { 10514 return states[url] == LOADED; 10515 }; 10516 10517 this.markDone = function(url) { 10518 states[url] = LOADED; 10519 }; 10520 10521 this.add = this.load = function(url, callback, scope) { 10522 var item, state = states[url]; 10523 10524 // Add url to load queue 10525 if (state == undef) { 10526 queue.push(url); 10527 states[url] = QUEUED; 10528 } 10529 10530 if (callback) { 10531 // Store away callback for later execution 10532 if (!scriptLoadedCallbacks[url]) 10533 scriptLoadedCallbacks[url] = []; 10534 10535 scriptLoadedCallbacks[url].push({ 10536 func : callback, 10537 scope : scope || this 10538 }); 10539 } 10540 }; 10541 10542 this.loadQueue = function(callback, scope) { 10543 this.loadScripts(queue, callback, scope); 10544 }; 10545 10546 this.loadScripts = function(scripts, callback, scope) { 10547 var loadScripts; 10548 10549 function execScriptLoadedCallbacks(url) { 10550 // Execute URL callback functions 10551 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10552 callback.func.call(callback.scope); 10553 }); 10554 10555 scriptLoadedCallbacks[url] = undef; 10556 }; 10557 10558 queueLoadedCallbacks.push({ 10559 func : callback, 10560 scope : scope || this 10561 }); 10562 10563 loadScripts = function() { 10564 var loadingScripts = tinymce.grep(scripts); 10565 10566 // Current scripts has been handled 10567 scripts.length = 0; 10568 10569 // Load scripts that needs to be loaded 10570 tinymce.each(loadingScripts, function(url) { 10571 // Script is already loaded then execute script callbacks directly 10572 if (states[url] == LOADED) { 10573 execScriptLoadedCallbacks(url); 10574 return; 10575 } 10576 10577 // Is script not loading then start loading it 10578 if (states[url] != LOADING) { 10579 states[url] = LOADING; 10580 loading++; 10581 10582 loadScript(url, function() { 10583 states[url] = LOADED; 10584 loading--; 10585 10586 execScriptLoadedCallbacks(url); 10587 10588 // Load more scripts if they where added by the recently loaded script 10589 loadScripts(); 10590 }); 10591 } 10592 }); 10593 10594 // No scripts are currently loading then execute all pending queue loaded callbacks 10595 if (!loading) { 10596 tinymce.each(queueLoadedCallbacks, function(callback) { 10597 callback.func.call(callback.scope); 10598 }); 10599 10600 queueLoadedCallbacks.length = 0; 10601 } 10602 }; 10603 10604 loadScripts(); 10605 }; 10606 }; 10607 10608 // Global script loader 10609 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10610 })(tinymce); 10611 10612 (function(tinymce) { 10613 tinymce.dom.RangeUtils = function(dom) { 10614 var INVISIBLE_CHAR = '\uFEFF'; 10615 10616 this.walk = function(rng, callback) { 10617 var startContainer = rng.startContainer, 10618 startOffset = rng.startOffset, 10619 endContainer = rng.endContainer, 10620 endOffset = rng.endOffset, 10621 ancestor, startPoint, 10622 endPoint, node, parent, siblings, nodes; 10623 10624 // Handle table cell selection the table plugin enables 10625 // you to fake select table cells and perform formatting actions on them 10626 nodes = dom.select('td.mceSelected,th.mceSelected'); 10627 if (nodes.length > 0) { 10628 tinymce.each(nodes, function(node) { 10629 callback([node]); 10630 }); 10631 10632 return; 10633 } 10634 10635 function exclude(nodes) { 10636 var node; 10637 10638 // First node is excluded 10639 node = nodes[0]; 10640 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10641 nodes.splice(0, 1); 10642 } 10643 10644 // Last node is excluded 10645 node = nodes[nodes.length - 1]; 10646 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 10647 nodes.splice(nodes.length - 1, 1); 10648 } 10649 10650 return nodes; 10651 }; 10652 10653 function collectSiblings(node, name, end_node) { 10654 var siblings = []; 10655 10656 for (; node && node != end_node; node = node[name]) 10657 siblings.push(node); 10658 10659 return siblings; 10660 }; 10661 10662 function findEndPoint(node, root) { 10663 do { 10664 if (node.parentNode == root) 10665 return node; 10666 10667 node = node.parentNode; 10668 } while(node); 10669 }; 10670 10671 function walkBoundary(start_node, end_node, next) { 10672 var siblingName = next ? 'nextSibling' : 'previousSibling'; 10673 10674 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 10675 parent = node.parentNode; 10676 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 10677 10678 if (siblings.length) { 10679 if (!next) 10680 siblings.reverse(); 10681 10682 callback(exclude(siblings)); 10683 } 10684 } 10685 }; 10686 10687 // If index based start position then resolve it 10688 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 10689 startContainer = startContainer.childNodes[startOffset]; 10690 10691 // If index based end position then resolve it 10692 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 10693 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 10694 10695 // Same container 10696 if (startContainer == endContainer) 10697 return callback(exclude([startContainer])); 10698 10699 // Find common ancestor and end points 10700 ancestor = dom.findCommonAncestor(startContainer, endContainer); 10701 10702 // Process left side 10703 for (node = startContainer; node; node = node.parentNode) { 10704 if (node === endContainer) 10705 return walkBoundary(startContainer, ancestor, true); 10706 10707 if (node === ancestor) 10708 break; 10709 } 10710 10711 // Process right side 10712 for (node = endContainer; node; node = node.parentNode) { 10713 if (node === startContainer) 10714 return walkBoundary(endContainer, ancestor); 10715 10716 if (node === ancestor) 10717 break; 10718 } 10719 10720 // Find start/end point 10721 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 10722 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 10723 10724 // Walk left leaf 10725 walkBoundary(startContainer, startPoint, true); 10726 10727 // Walk the middle from start to end point 10728 siblings = collectSiblings( 10729 startPoint == startContainer ? startPoint : startPoint.nextSibling, 10730 'nextSibling', 10731 endPoint == endContainer ? endPoint.nextSibling : endPoint 10732 ); 10733 10734 if (siblings.length) 10735 callback(exclude(siblings)); 10736 10737 // Walk right leaf 10738 walkBoundary(endContainer, endPoint); 10739 }; 10740 10741 this.split = function(rng) { 10742 var startContainer = rng.startContainer, 10743 startOffset = rng.startOffset, 10744 endContainer = rng.endContainer, 10745 endOffset = rng.endOffset; 10746 10747 function splitText(node, offset) { 10748 return node.splitText(offset); 10749 }; 10750 10751 // Handle single text node 10752 if (startContainer == endContainer && startContainer.nodeType == 3) { 10753 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10754 endContainer = splitText(startContainer, startOffset); 10755 startContainer = endContainer.previousSibling; 10756 10757 if (endOffset > startOffset) { 10758 endOffset = endOffset - startOffset; 10759 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 10760 endOffset = endContainer.nodeValue.length; 10761 startOffset = 0; 10762 } else { 10763 endOffset = 0; 10764 } 10765 } 10766 } else { 10767 // Split startContainer text node if needed 10768 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10769 startContainer = splitText(startContainer, startOffset); 10770 startOffset = 0; 10771 } 10772 10773 // Split endContainer text node if needed 10774 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 10775 endContainer = splitText(endContainer, endOffset).previousSibling; 10776 endOffset = endContainer.nodeValue.length; 10777 } 10778 } 10779 10780 return { 10781 startContainer : startContainer, 10782 startOffset : startOffset, 10783 endContainer : endContainer, 10784 endOffset : endOffset 10785 }; 10786 }; 10787 10788 }; 10789 10790 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 10791 if (rng1 && rng2) { 10792 // Compare native IE ranges 10793 if (rng1.item || rng1.duplicate) { 10794 // Both are control ranges and the selected element matches 10795 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 10796 return true; 10797 10798 // Both are text ranges and the range matches 10799 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 10800 return true; 10801 } else { 10802 // Compare w3c ranges 10803 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 10804 } 10805 } 10806 10807 return false; 10808 }; 10809 })(tinymce); 10810 10811 (function(tinymce) { 10812 var Event = tinymce.dom.Event, each = tinymce.each; 10813 10814 tinymce.create('tinymce.ui.KeyboardNavigation', { 10815 KeyboardNavigation: function(settings, dom) { 10816 var t = this, root = settings.root, items = settings.items, 10817 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 10818 excludeFromTabOrder = settings.excludeFromTabOrder, 10819 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 10820 10821 dom = dom || tinymce.DOM; 10822 10823 itemFocussed = function(evt) { 10824 focussedId = evt.target.id; 10825 }; 10826 10827 itemBlurred = function(evt) { 10828 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 10829 }; 10830 10831 rootFocussed = function(evt) { 10832 var item = dom.get(focussedId); 10833 dom.setAttrib(item, 'tabindex', '0'); 10834 item.focus(); 10835 }; 10836 10837 t.focus = function() { 10838 dom.get(focussedId).focus(); 10839 }; 10840 10841 t.destroy = function() { 10842 each(items, function(item) { 10843 var elm = dom.get(item.id); 10844 10845 dom.unbind(elm, 'focus', itemFocussed); 10846 dom.unbind(elm, 'blur', itemBlurred); 10847 }); 10848 10849 var rootElm = dom.get(root); 10850 dom.unbind(rootElm, 'focus', rootFocussed); 10851 dom.unbind(rootElm, 'keydown', rootKeydown); 10852 10853 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 10854 t.destroy = function() {}; 10855 }; 10856 10857 t.moveFocus = function(dir, evt) { 10858 var idx = -1, controls = t.controls, newFocus; 10859 10860 if (!focussedId) 10861 return; 10862 10863 each(items, function(item, index) { 10864 if (item.id === focussedId) { 10865 idx = index; 10866 return false; 10867 } 10868 }); 10869 10870 idx += dir; 10871 if (idx < 0) { 10872 idx = items.length - 1; 10873 } else if (idx >= items.length) { 10874 idx = 0; 10875 } 10876 10877 newFocus = items[idx]; 10878 dom.setAttrib(focussedId, 'tabindex', '-1'); 10879 dom.setAttrib(newFocus.id, 'tabindex', '0'); 10880 dom.get(newFocus.id).focus(); 10881 10882 if (settings.actOnFocus) { 10883 settings.onAction(newFocus.id); 10884 } 10885 10886 if (evt) 10887 Event.cancel(evt); 10888 }; 10889 10890 rootKeydown = function(evt) { 10891 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 10892 10893 switch (evt.keyCode) { 10894 case DOM_VK_LEFT: 10895 if (enableLeftRight) t.moveFocus(-1); 10896 break; 10897 10898 case DOM_VK_RIGHT: 10899 if (enableLeftRight) t.moveFocus(1); 10900 break; 10901 10902 case DOM_VK_UP: 10903 if (enableUpDown) t.moveFocus(-1); 10904 break; 10905 10906 case DOM_VK_DOWN: 10907 if (enableUpDown) t.moveFocus(1); 10908 break; 10909 10910 case DOM_VK_ESCAPE: 10911 if (settings.onCancel) { 10912 settings.onCancel(); 10913 Event.cancel(evt); 10914 } 10915 break; 10916 10917 case DOM_VK_ENTER: 10918 case DOM_VK_RETURN: 10919 case DOM_VK_SPACE: 10920 if (settings.onAction) { 10921 settings.onAction(focussedId); 10922 Event.cancel(evt); 10923 } 10924 break; 10925 } 10926 }; 10927 10928 // Set up state and listeners for each item. 10929 each(items, function(item, idx) { 10930 var tabindex, elm; 10931 10932 if (!item.id) { 10933 item.id = dom.uniqueId('_mce_item_'); 10934 } 10935 10936 elm = dom.get(item.id); 10937 10938 if (excludeFromTabOrder) { 10939 dom.bind(elm, 'blur', itemBlurred); 10940 tabindex = '-1'; 10941 } else { 10942 tabindex = (idx === 0 ? '0' : '-1'); 10943 } 10944 10945 elm.setAttribute('tabindex', tabindex); 10946 dom.bind(elm, 'focus', itemFocussed); 10947 }); 10948 10949 // Setup initial state for root element. 10950 if (items[0]){ 10951 focussedId = items[0].id; 10952 } 10953 10954 dom.setAttrib(root, 'tabindex', '-1'); 10955 10956 // Setup listeners for root element. 10957 var rootElm = dom.get(root); 10958 dom.bind(rootElm, 'focus', rootFocussed); 10959 dom.bind(rootElm, 'keydown', rootKeydown); 10960 } 10961 }); 10962 })(tinymce); 10963 10964 (function(tinymce) { 10965 // Shorten class names 10966 var DOM = tinymce.DOM, is = tinymce.is; 10967 10968 tinymce.create('tinymce.ui.Control', { 10969 Control : function(id, s, editor) { 10970 this.id = id; 10971 this.settings = s = s || {}; 10972 this.rendered = false; 10973 this.onRender = new tinymce.util.Dispatcher(this); 10974 this.classPrefix = ''; 10975 this.scope = s.scope || this; 10976 this.disabled = 0; 10977 this.active = 0; 10978 this.editor = editor; 10979 }, 10980 10981 setAriaProperty : function(property, value) { 10982 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 10983 if (element) { 10984 DOM.setAttrib(element, 'aria-' + property, !!value); 10985 } 10986 }, 10987 10988 focus : function() { 10989 DOM.get(this.id).focus(); 10990 }, 10991 10992 setDisabled : function(s) { 10993 if (s != this.disabled) { 10994 this.setAriaProperty('disabled', s); 10995 10996 this.setState('Disabled', s); 10997 this.setState('Enabled', !s); 10998 this.disabled = s; 10999 } 11000 }, 11001 11002 isDisabled : function() { 11003 return this.disabled; 11004 }, 11005 11006 setActive : function(s) { 11007 if (s != this.active) { 11008 this.setState('Active', s); 11009 this.active = s; 11010 this.setAriaProperty('pressed', s); 11011 } 11012 }, 11013 11014 isActive : function() { 11015 return this.active; 11016 }, 11017 11018 setState : function(c, s) { 11019 var n = DOM.get(this.id); 11020 11021 c = this.classPrefix + c; 11022 11023 if (s) 11024 DOM.addClass(n, c); 11025 else 11026 DOM.removeClass(n, c); 11027 }, 11028 11029 isRendered : function() { 11030 return this.rendered; 11031 }, 11032 11033 renderHTML : function() { 11034 }, 11035 11036 renderTo : function(n) { 11037 DOM.setHTML(n, this.renderHTML()); 11038 }, 11039 11040 postRender : function() { 11041 var t = this, b; 11042 11043 // Set pending states 11044 if (is(t.disabled)) { 11045 b = t.disabled; 11046 t.disabled = -1; 11047 t.setDisabled(b); 11048 } 11049 11050 if (is(t.active)) { 11051 b = t.active; 11052 t.active = -1; 11053 t.setActive(b); 11054 } 11055 }, 11056 11057 remove : function() { 11058 DOM.remove(this.id); 11059 this.destroy(); 11060 }, 11061 11062 destroy : function() { 11063 tinymce.dom.Event.clear(this.id); 11064 } 11065 }); 11066 })(tinymce); 11067 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11068 Container : function(id, s, editor) { 11069 this.parent(id, s, editor); 11070 11071 this.controls = []; 11072 11073 this.lookup = {}; 11074 }, 11075 11076 add : function(c) { 11077 this.lookup[c.id] = c; 11078 this.controls.push(c); 11079 11080 return c; 11081 }, 11082 11083 get : function(n) { 11084 return this.lookup[n]; 11085 } 11086 }); 11087 11088 11089 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11090 Separator : function(id, s) { 11091 this.parent(id, s); 11092 this.classPrefix = 'mceSeparator'; 11093 this.setDisabled(true); 11094 }, 11095 11096 renderHTML : function() { 11097 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11098 } 11099 }); 11100 11101 (function(tinymce) { 11102 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11103 11104 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11105 MenuItem : function(id, s) { 11106 this.parent(id, s); 11107 this.classPrefix = 'mceMenuItem'; 11108 }, 11109 11110 setSelected : function(s) { 11111 this.setState('Selected', s); 11112 this.setAriaProperty('checked', !!s); 11113 this.selected = s; 11114 }, 11115 11116 isSelected : function() { 11117 return this.selected; 11118 }, 11119 11120 postRender : function() { 11121 var t = this; 11122 11123 t.parent(); 11124 11125 // Set pending state 11126 if (is(t.selected)) 11127 t.setSelected(t.selected); 11128 } 11129 }); 11130 })(tinymce); 11131 11132 (function(tinymce) { 11133 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11134 11135 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11136 Menu : function(id, s) { 11137 var t = this; 11138 11139 t.parent(id, s); 11140 t.items = {}; 11141 t.collapsed = false; 11142 t.menuCount = 0; 11143 t.onAddItem = new tinymce.util.Dispatcher(this); 11144 }, 11145 11146 expand : function(d) { 11147 var t = this; 11148 11149 if (d) { 11150 walk(t, function(o) { 11151 if (o.expand) 11152 o.expand(); 11153 }, 'items', t); 11154 } 11155 11156 t.collapsed = false; 11157 }, 11158 11159 collapse : function(d) { 11160 var t = this; 11161 11162 if (d) { 11163 walk(t, function(o) { 11164 if (o.collapse) 11165 o.collapse(); 11166 }, 'items', t); 11167 } 11168 11169 t.collapsed = true; 11170 }, 11171 11172 isCollapsed : function() { 11173 return this.collapsed; 11174 }, 11175 11176 add : function(o) { 11177 if (!o.settings) 11178 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11179 11180 this.onAddItem.dispatch(this, o); 11181 11182 return this.items[o.id] = o; 11183 }, 11184 11185 addSeparator : function() { 11186 return this.add({separator : true}); 11187 }, 11188 11189 addMenu : function(o) { 11190 if (!o.collapse) 11191 o = this.createMenu(o); 11192 11193 this.menuCount++; 11194 11195 return this.add(o); 11196 }, 11197 11198 hasMenus : function() { 11199 return this.menuCount !== 0; 11200 }, 11201 11202 remove : function(o) { 11203 delete this.items[o.id]; 11204 }, 11205 11206 removeAll : function() { 11207 var t = this; 11208 11209 walk(t, function(o) { 11210 if (o.removeAll) 11211 o.removeAll(); 11212 else 11213 o.remove(); 11214 11215 o.destroy(); 11216 }, 'items', t); 11217 11218 t.items = {}; 11219 }, 11220 11221 createMenu : function(o) { 11222 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11223 11224 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11225 11226 return m; 11227 } 11228 }); 11229 })(tinymce); 11230 (function(tinymce) { 11231 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11232 11233 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11234 DropMenu : function(id, s) { 11235 s = s || {}; 11236 s.container = s.container || DOM.doc.body; 11237 s.offset_x = s.offset_x || 0; 11238 s.offset_y = s.offset_y || 0; 11239 s.vp_offset_x = s.vp_offset_x || 0; 11240 s.vp_offset_y = s.vp_offset_y || 0; 11241 11242 if (is(s.icons) && !s.icons) 11243 s['class'] += ' mceNoIcons'; 11244 11245 this.parent(id, s); 11246 this.onShowMenu = new tinymce.util.Dispatcher(this); 11247 this.onHideMenu = new tinymce.util.Dispatcher(this); 11248 this.classPrefix = 'mceMenu'; 11249 }, 11250 11251 createMenu : function(s) { 11252 var t = this, cs = t.settings, m; 11253 11254 s.container = s.container || cs.container; 11255 s.parent = t; 11256 s.constrain = s.constrain || cs.constrain; 11257 s['class'] = s['class'] || cs['class']; 11258 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11259 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11260 s.keyboard_focus = cs.keyboard_focus; 11261 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11262 11263 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11264 11265 return m; 11266 }, 11267 11268 focus : function() { 11269 var t = this; 11270 if (t.keyboardNav) { 11271 t.keyboardNav.focus(); 11272 } 11273 }, 11274 11275 update : function() { 11276 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11277 11278 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11279 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11280 11281 if (!DOM.boxModel) 11282 t.element.setStyles({width : tw + 2, height : th + 2}); 11283 else 11284 t.element.setStyles({width : tw, height : th}); 11285 11286 if (s.max_width) 11287 DOM.setStyle(co, 'width', tw); 11288 11289 if (s.max_height) { 11290 DOM.setStyle(co, 'height', th); 11291 11292 if (tb.clientHeight < s.max_height) 11293 DOM.setStyle(co, 'overflow', 'hidden'); 11294 } 11295 }, 11296 11297 showMenu : function(x, y, px) { 11298 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11299 11300 t.collapse(1); 11301 11302 if (t.isMenuVisible) 11303 return; 11304 11305 if (!t.rendered) { 11306 co = DOM.add(t.settings.container, t.renderNode()); 11307 11308 each(t.items, function(o) { 11309 o.postRender(); 11310 }); 11311 11312 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11313 } else 11314 co = DOM.get('menu_' + t.id); 11315 11316 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11317 if (!tinymce.isOpera) 11318 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11319 11320 DOM.show(co); 11321 t.update(); 11322 11323 x += s.offset_x || 0; 11324 y += s.offset_y || 0; 11325 vp.w -= 4; 11326 vp.h -= 4; 11327 11328 // Move inside viewport if not submenu 11329 if (s.constrain) { 11330 w = co.clientWidth - ot; 11331 h = co.clientHeight - ot; 11332 mx = vp.x + vp.w; 11333 my = vp.y + vp.h; 11334 11335 if ((x + s.vp_offset_x + w) > mx) 11336 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11337 11338 if ((y + s.vp_offset_y + h) > my) 11339 y = Math.max(0, (my - s.vp_offset_y) - h); 11340 } 11341 11342 DOM.setStyles(co, {left : x , top : y}); 11343 t.element.update(); 11344 11345 t.isMenuVisible = 1; 11346 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11347 var m; 11348 11349 e = e.target; 11350 11351 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11352 m = t.items[e.id]; 11353 11354 if (m.isDisabled()) 11355 return; 11356 11357 dm = t; 11358 11359 while (dm) { 11360 if (dm.hideMenu) 11361 dm.hideMenu(); 11362 11363 dm = dm.settings.parent; 11364 } 11365 11366 if (m.settings.onclick) 11367 m.settings.onclick(e); 11368 11369 return false; // Cancel to fix onbeforeunload problem 11370 } 11371 }); 11372 11373 if (t.hasMenus()) { 11374 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11375 var m, r, mi; 11376 11377 e = e.target; 11378 if (e && (e = DOM.getParent(e, 'tr'))) { 11379 m = t.items[e.id]; 11380 11381 if (t.lastMenu) 11382 t.lastMenu.collapse(1); 11383 11384 if (m.isDisabled()) 11385 return; 11386 11387 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11388 //p = DOM.getPos(s.container); 11389 r = DOM.getRect(e); 11390 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11391 t.lastMenu = m; 11392 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11393 } 11394 } 11395 }); 11396 } 11397 11398 Event.add(co, 'keydown', t._keyHandler, t); 11399 11400 t.onShowMenu.dispatch(t); 11401 11402 if (s.keyboard_focus) { 11403 t._setupKeyboardNav(); 11404 } 11405 }, 11406 11407 hideMenu : function(c) { 11408 var t = this, co = DOM.get('menu_' + t.id), e; 11409 11410 if (!t.isMenuVisible) 11411 return; 11412 11413 if (t.keyboardNav) t.keyboardNav.destroy(); 11414 Event.remove(co, 'mouseover', t.mouseOverFunc); 11415 Event.remove(co, 'click', t.mouseClickFunc); 11416 Event.remove(co, 'keydown', t._keyHandler); 11417 DOM.hide(co); 11418 t.isMenuVisible = 0; 11419 11420 if (!c) 11421 t.collapse(1); 11422 11423 if (t.element) 11424 t.element.hide(); 11425 11426 if (e = DOM.get(t.id)) 11427 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11428 11429 t.onHideMenu.dispatch(t); 11430 }, 11431 11432 add : function(o) { 11433 var t = this, co; 11434 11435 o = t.parent(o); 11436 11437 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11438 t._add(DOM.select('tbody', co)[0], o); 11439 11440 return o; 11441 }, 11442 11443 collapse : function(d) { 11444 this.parent(d); 11445 this.hideMenu(1); 11446 }, 11447 11448 remove : function(o) { 11449 DOM.remove(o.id); 11450 this.destroy(); 11451 11452 return this.parent(o); 11453 }, 11454 11455 destroy : function() { 11456 var t = this, co = DOM.get('menu_' + t.id); 11457 11458 if (t.keyboardNav) t.keyboardNav.destroy(); 11459 Event.remove(co, 'mouseover', t.mouseOverFunc); 11460 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11461 Event.remove(co, 'click', t.mouseClickFunc); 11462 Event.remove(co, 'keydown', t._keyHandler); 11463 11464 if (t.element) 11465 t.element.remove(); 11466 11467 DOM.remove(co); 11468 }, 11469 11470 renderNode : function() { 11471 var t = this, s = t.settings, n, tb, co, w; 11472 11473 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); 11474 if (t.settings.parent) { 11475 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11476 } 11477 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11478 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11479 11480 if (s.menu_line) 11481 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11482 11483 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11484 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11485 tb = DOM.add(n, 'tbody'); 11486 11487 each(t.items, function(o) { 11488 t._add(tb, o); 11489 }); 11490 11491 t.rendered = true; 11492 11493 return w; 11494 }, 11495 11496 // Internal functions 11497 _setupKeyboardNav : function(){ 11498 var contextMenu, menuItems, t=this; 11499 contextMenu = DOM.get('menu_' + t.id); 11500 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11501 menuItems.splice(0,0,contextMenu); 11502 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11503 root: 'menu_' + t.id, 11504 items: menuItems, 11505 onCancel: function() { 11506 t.hideMenu(); 11507 }, 11508 enableUpDown: true 11509 }); 11510 contextMenu.focus(); 11511 }, 11512 11513 _keyHandler : function(evt) { 11514 var t = this, e; 11515 switch (evt.keyCode) { 11516 case 37: // Left 11517 if (t.settings.parent) { 11518 t.hideMenu(); 11519 t.settings.parent.focus(); 11520 Event.cancel(evt); 11521 } 11522 break; 11523 case 39: // Right 11524 if (t.mouseOverFunc) 11525 t.mouseOverFunc(evt); 11526 break; 11527 } 11528 }, 11529 11530 _add : function(tb, o) { 11531 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11532 11533 if (s.separator) { 11534 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11535 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11536 11537 if (n = ro.previousSibling) 11538 DOM.addClass(n, 'mceLast'); 11539 11540 return; 11541 } 11542 11543 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11544 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11545 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11546 11547 if (s.parent) { 11548 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11549 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11550 } 11551 11552 DOM.addClass(it, s['class']); 11553 // n = DOM.add(n, 'span', {'class' : 'item'}); 11554 11555 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11556 11557 if (s.icon_src) 11558 DOM.add(ic, 'img', {src : s.icon_src}); 11559 11560 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11561 11562 if (o.settings.style) { 11563 if (typeof o.settings.style == "function") 11564 o.settings.style = o.settings.style(); 11565 11566 DOM.setAttrib(n, 'style', o.settings.style); 11567 } 11568 11569 if (tb.childNodes.length == 1) 11570 DOM.addClass(ro, 'mceFirst'); 11571 11572 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11573 DOM.addClass(ro, 'mceFirst'); 11574 11575 if (o.collapse) 11576 DOM.addClass(ro, cp + 'ItemSub'); 11577 11578 if (n = ro.previousSibling) 11579 DOM.removeClass(n, 'mceLast'); 11580 11581 DOM.addClass(ro, 'mceLast'); 11582 } 11583 }); 11584 })(tinymce); 11585 (function(tinymce) { 11586 var DOM = tinymce.DOM; 11587 11588 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11589 Button : function(id, s, ed) { 11590 this.parent(id, s, ed); 11591 this.classPrefix = 'mceButton'; 11592 }, 11593 11594 renderHTML : function() { 11595 var cp = this.classPrefix, s = this.settings, h, l; 11596 11597 l = DOM.encode(s.label || ''); 11598 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">'; 11599 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11600 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11601 else 11602 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11603 11604 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11605 h += '</a>'; 11606 return h; 11607 }, 11608 11609 postRender : function() { 11610 var t = this, s = t.settings, imgBookmark; 11611 11612 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11613 // need to keep the selection in case the selection is lost 11614 if (tinymce.isIE && t.editor) { 11615 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11616 var nodeName = t.editor.selection.getNode().nodeName; 11617 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11618 }); 11619 } 11620 tinymce.dom.Event.add(t.id, 'click', function(e) { 11621 if (!t.isDisabled()) { 11622 // restore the selection in case the selection is lost in IE 11623 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11624 t.editor.selection.moveToBookmark(imgBookmark); 11625 } 11626 return s.onclick.call(s.scope, e); 11627 } 11628 }); 11629 tinymce.dom.Event.add(t.id, 'keyup', function(e) { 11630 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) 11631 return s.onclick.call(s.scope, e); 11632 }); 11633 } 11634 }); 11635 })(tinymce); 11636 11637 (function(tinymce) { 11638 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11639 11640 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11641 ListBox : function(id, s, ed) { 11642 var t = this; 11643 11644 t.parent(id, s, ed); 11645 11646 t.items = []; 11647 11648 t.onChange = new Dispatcher(t); 11649 11650 t.onPostRender = new Dispatcher(t); 11651 11652 t.onAdd = new Dispatcher(t); 11653 11654 t.onRenderMenu = new tinymce.util.Dispatcher(this); 11655 11656 t.classPrefix = 'mceListBox'; 11657 t.marked = {}; 11658 }, 11659 11660 select : function(va) { 11661 var t = this, fv, f; 11662 11663 t.marked = {}; 11664 11665 if (va == undef) 11666 return t.selectByIndex(-1); 11667 11668 // Is string or number make function selector 11669 if (va && typeof(va)=="function") 11670 f = va; 11671 else { 11672 f = function(v) { 11673 return v == va; 11674 }; 11675 } 11676 11677 // Do we need to do something? 11678 if (va != t.selectedValue) { 11679 // Find item 11680 each(t.items, function(o, i) { 11681 if (f(o.value)) { 11682 fv = 1; 11683 t.selectByIndex(i); 11684 return false; 11685 } 11686 }); 11687 11688 if (!fv) 11689 t.selectByIndex(-1); 11690 } 11691 }, 11692 11693 selectByIndex : function(idx) { 11694 var t = this, e, o, label; 11695 11696 t.marked = {}; 11697 11698 if (idx != t.selectedIndex) { 11699 e = DOM.get(t.id + '_text'); 11700 label = DOM.get(t.id + '_voiceDesc'); 11701 o = t.items[idx]; 11702 11703 if (o) { 11704 t.selectedValue = o.value; 11705 t.selectedIndex = idx; 11706 DOM.setHTML(e, DOM.encode(o.title)); 11707 DOM.setHTML(label, t.settings.title + " - " + o.title); 11708 DOM.removeClass(e, 'mceTitle'); 11709 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 11710 } else { 11711 DOM.setHTML(e, DOM.encode(t.settings.title)); 11712 DOM.setHTML(label, DOM.encode(t.settings.title)); 11713 DOM.addClass(e, 'mceTitle'); 11714 t.selectedValue = t.selectedIndex = null; 11715 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 11716 } 11717 e = 0; 11718 } 11719 }, 11720 11721 mark : function(value) { 11722 this.marked[value] = true; 11723 }, 11724 11725 add : function(n, v, o) { 11726 var t = this; 11727 11728 o = o || {}; 11729 o = tinymce.extend(o, { 11730 title : n, 11731 value : v 11732 }); 11733 11734 t.items.push(o); 11735 t.onAdd.dispatch(t, o); 11736 }, 11737 11738 getLength : function() { 11739 return this.items.length; 11740 }, 11741 11742 renderHTML : function() { 11743 var h = '', t = this, s = t.settings, cp = t.classPrefix; 11744 11745 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 11746 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 11747 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>'; 11748 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>'; 11749 h += '</tr></tbody></table></span>'; 11750 11751 return h; 11752 }, 11753 11754 showMenu : function() { 11755 var t = this, p2, e = DOM.get(this.id), m; 11756 11757 if (t.isDisabled() || t.items.length === 0) 11758 return; 11759 11760 if (t.menu && t.menu.isMenuVisible) 11761 return t.hideMenu(); 11762 11763 if (!t.isMenuRendered) { 11764 t.renderMenu(); 11765 t.isMenuRendered = true; 11766 } 11767 11768 p2 = DOM.getPos(e); 11769 11770 m = t.menu; 11771 m.settings.offset_x = p2.x; 11772 m.settings.offset_y = p2.y; 11773 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 11774 11775 // Select in menu 11776 each(t.items, function(o) { 11777 if (m.items[o.id]) { 11778 m.items[o.id].setSelected(0); 11779 } 11780 }); 11781 11782 each(t.items, function(o) { 11783 if (m.items[o.id] && t.marked[o.value]) { 11784 m.items[o.id].setSelected(1); 11785 } 11786 11787 if (o.value === t.selectedValue) { 11788 m.items[o.id].setSelected(1); 11789 } 11790 }); 11791 11792 m.showMenu(0, e.clientHeight); 11793 11794 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 11795 DOM.addClass(t.id, t.classPrefix + 'Selected'); 11796 11797 //DOM.get(t.id + '_text').focus(); 11798 }, 11799 11800 hideMenu : function(e) { 11801 var t = this; 11802 11803 if (t.menu && t.menu.isMenuVisible) { 11804 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11805 11806 // Prevent double toogles by canceling the mouse click event to the button 11807 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 11808 return; 11809 11810 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 11811 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11812 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 11813 t.menu.hideMenu(); 11814 } 11815 } 11816 }, 11817 11818 renderMenu : function() { 11819 var t = this, m; 11820 11821 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 11822 menu_line : 1, 11823 'class' : t.classPrefix + 'Menu mceNoIcons', 11824 max_width : 250, 11825 max_height : 150 11826 }); 11827 11828 m.onHideMenu.add(function() { 11829 t.hideMenu(); 11830 t.focus(); 11831 }); 11832 11833 m.add({ 11834 title : t.settings.title, 11835 'class' : 'mceMenuItemTitle', 11836 onclick : function() { 11837 if (t.settings.onselect('') !== false) 11838 t.select(''); // Must be runned after 11839 } 11840 }); 11841 11842 each(t.items, function(o) { 11843 // No value then treat it as a title 11844 if (o.value === undef) { 11845 m.add({ 11846 title : o.title, 11847 role : "option", 11848 'class' : 'mceMenuItemTitle', 11849 onclick : function() { 11850 if (t.settings.onselect('') !== false) 11851 t.select(''); // Must be runned after 11852 } 11853 }); 11854 } else { 11855 o.id = DOM.uniqueId(); 11856 o.role= "option"; 11857 o.onclick = function() { 11858 if (t.settings.onselect(o.value) !== false) 11859 t.select(o.value); // Must be runned after 11860 }; 11861 11862 m.add(o); 11863 } 11864 }); 11865 11866 t.onRenderMenu.dispatch(t, m); 11867 t.menu = m; 11868 }, 11869 11870 postRender : function() { 11871 var t = this, cp = t.classPrefix; 11872 11873 Event.add(t.id, 'click', t.showMenu, t); 11874 Event.add(t.id, 'keydown', function(evt) { 11875 if (evt.keyCode == 32) { // Space 11876 t.showMenu(evt); 11877 Event.cancel(evt); 11878 } 11879 }); 11880 Event.add(t.id, 'focus', function() { 11881 if (!t._focused) { 11882 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 11883 if (e.keyCode == 40) { 11884 t.showMenu(); 11885 Event.cancel(e); 11886 } 11887 }); 11888 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 11889 var v; 11890 if (e.keyCode == 13) { 11891 // Fake select on enter 11892 v = t.selectedValue; 11893 t.selectedValue = null; // Needs to be null to fake change 11894 Event.cancel(e); 11895 t.settings.onselect(v); 11896 } 11897 }); 11898 } 11899 11900 t._focused = 1; 11901 }); 11902 Event.add(t.id, 'blur', function() { 11903 Event.remove(t.id, 'keydown', t.keyDownHandler); 11904 Event.remove(t.id, 'keypress', t.keyPressHandler); 11905 t._focused = 0; 11906 }); 11907 11908 // Old IE doesn't have hover on all elements 11909 if (tinymce.isIE6 || !DOM.boxModel) { 11910 Event.add(t.id, 'mouseover', function() { 11911 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11912 DOM.addClass(t.id, cp + 'Hover'); 11913 }); 11914 11915 Event.add(t.id, 'mouseout', function() { 11916 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11917 DOM.removeClass(t.id, cp + 'Hover'); 11918 }); 11919 } 11920 11921 t.onPostRender.dispatch(t, DOM.get(t.id)); 11922 }, 11923 11924 destroy : function() { 11925 this.parent(); 11926 11927 Event.clear(this.id + '_text'); 11928 Event.clear(this.id + '_open'); 11929 } 11930 }); 11931 })(tinymce); 11932 11933 (function(tinymce) { 11934 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11935 11936 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 11937 NativeListBox : function(id, s) { 11938 this.parent(id, s); 11939 this.classPrefix = 'mceNativeListBox'; 11940 }, 11941 11942 setDisabled : function(s) { 11943 DOM.get(this.id).disabled = s; 11944 this.setAriaProperty('disabled', s); 11945 }, 11946 11947 isDisabled : function() { 11948 return DOM.get(this.id).disabled; 11949 }, 11950 11951 select : function(va) { 11952 var t = this, fv, f; 11953 11954 if (va == undef) 11955 return t.selectByIndex(-1); 11956 11957 // Is string or number make function selector 11958 if (va && typeof(va)=="function") 11959 f = va; 11960 else { 11961 f = function(v) { 11962 return v == va; 11963 }; 11964 } 11965 11966 // Do we need to do something? 11967 if (va != t.selectedValue) { 11968 // Find item 11969 each(t.items, function(o, i) { 11970 if (f(o.value)) { 11971 fv = 1; 11972 t.selectByIndex(i); 11973 return false; 11974 } 11975 }); 11976 11977 if (!fv) 11978 t.selectByIndex(-1); 11979 } 11980 }, 11981 11982 selectByIndex : function(idx) { 11983 DOM.get(this.id).selectedIndex = idx + 1; 11984 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 11985 }, 11986 11987 add : function(n, v, a) { 11988 var o, t = this; 11989 11990 a = a || {}; 11991 a.value = v; 11992 11993 if (t.isRendered()) 11994 DOM.add(DOM.get(this.id), 'option', a, n); 11995 11996 o = { 11997 title : n, 11998 value : v, 11999 attribs : a 12000 }; 12001 12002 t.items.push(o); 12003 t.onAdd.dispatch(t, o); 12004 }, 12005 12006 getLength : function() { 12007 return this.items.length; 12008 }, 12009 12010 renderHTML : function() { 12011 var h, t = this; 12012 12013 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12014 12015 each(t.items, function(it) { 12016 h += DOM.createHTML('option', {value : it.value}, it.title); 12017 }); 12018 12019 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12020 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12021 return h; 12022 }, 12023 12024 postRender : function() { 12025 var t = this, ch, changeListenerAdded = true; 12026 12027 t.rendered = true; 12028 12029 function onChange(e) { 12030 var v = t.items[e.target.selectedIndex - 1]; 12031 12032 if (v && (v = v.value)) { 12033 t.onChange.dispatch(t, v); 12034 12035 if (t.settings.onselect) 12036 t.settings.onselect(v); 12037 } 12038 }; 12039 12040 Event.add(t.id, 'change', onChange); 12041 12042 // Accessibility keyhandler 12043 Event.add(t.id, 'keydown', function(e) { 12044 var bf; 12045 12046 Event.remove(t.id, 'change', ch); 12047 changeListenerAdded = false; 12048 12049 bf = Event.add(t.id, 'blur', function() { 12050 if (changeListenerAdded) return; 12051 changeListenerAdded = true; 12052 Event.add(t.id, 'change', onChange); 12053 Event.remove(t.id, 'blur', bf); 12054 }); 12055 12056 //prevent default left and right keys on chrome - so that the keyboard navigation is used. 12057 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { 12058 return Event.prevent(e); 12059 } 12060 12061 if (e.keyCode == 13 || e.keyCode == 32) { 12062 onChange(e); 12063 return Event.cancel(e); 12064 } 12065 }); 12066 12067 t.onPostRender.dispatch(t, DOM.get(t.id)); 12068 } 12069 }); 12070 })(tinymce); 12071 12072 (function(tinymce) { 12073 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12074 12075 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12076 MenuButton : function(id, s, ed) { 12077 this.parent(id, s, ed); 12078 12079 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12080 12081 s.menu_container = s.menu_container || DOM.doc.body; 12082 }, 12083 12084 showMenu : function() { 12085 var t = this, p1, p2, e = DOM.get(t.id), m; 12086 12087 if (t.isDisabled()) 12088 return; 12089 12090 if (!t.isMenuRendered) { 12091 t.renderMenu(); 12092 t.isMenuRendered = true; 12093 } 12094 12095 if (t.isMenuVisible) 12096 return t.hideMenu(); 12097 12098 p1 = DOM.getPos(t.settings.menu_container); 12099 p2 = DOM.getPos(e); 12100 12101 m = t.menu; 12102 m.settings.offset_x = p2.x; 12103 m.settings.offset_y = p2.y; 12104 m.settings.vp_offset_x = p2.x; 12105 m.settings.vp_offset_y = p2.y; 12106 m.settings.keyboard_focus = t._focused; 12107 m.showMenu(0, e.firstChild.clientHeight); 12108 12109 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12110 t.setState('Selected', 1); 12111 12112 t.isMenuVisible = 1; 12113 }, 12114 12115 renderMenu : function() { 12116 var t = this, m; 12117 12118 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12119 menu_line : 1, 12120 'class' : this.classPrefix + 'Menu', 12121 icons : t.settings.icons 12122 }); 12123 12124 m.onHideMenu.add(function() { 12125 t.hideMenu(); 12126 t.focus(); 12127 }); 12128 12129 t.onRenderMenu.dispatch(t, m); 12130 t.menu = m; 12131 }, 12132 12133 hideMenu : function(e) { 12134 var t = this; 12135 12136 // Prevent double toogles by canceling the mouse click event to the button 12137 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12138 return; 12139 12140 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12141 t.setState('Selected', 0); 12142 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12143 if (t.menu) 12144 t.menu.hideMenu(); 12145 } 12146 12147 t.isMenuVisible = 0; 12148 }, 12149 12150 postRender : function() { 12151 var t = this, s = t.settings; 12152 12153 Event.add(t.id, 'click', function() { 12154 if (!t.isDisabled()) { 12155 if (s.onclick) 12156 s.onclick(t.value); 12157 12158 t.showMenu(); 12159 } 12160 }); 12161 } 12162 }); 12163 })(tinymce); 12164 12165 (function(tinymce) { 12166 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12167 12168 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12169 SplitButton : function(id, s, ed) { 12170 this.parent(id, s, ed); 12171 this.classPrefix = 'mceSplitButton'; 12172 }, 12173 12174 renderHTML : function() { 12175 var h, t = this, s = t.settings, h1; 12176 12177 h = '<tbody><tr>'; 12178 12179 if (s.image) 12180 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12181 else 12182 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12183 12184 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12185 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12186 12187 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12188 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12189 12190 h += '</tr></tbody>'; 12191 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12192 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12193 }, 12194 12195 postRender : function() { 12196 var t = this, s = t.settings, activate; 12197 12198 if (s.onclick) { 12199 activate = function(evt) { 12200 if (!t.isDisabled()) { 12201 s.onclick(t.value); 12202 Event.cancel(evt); 12203 } 12204 }; 12205 Event.add(t.id + '_action', 'click', activate); 12206 Event.add(t.id, ['click', 'keydown'], function(evt) { 12207 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12208 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12209 activate(); 12210 Event.cancel(evt); 12211 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12212 t.showMenu(); 12213 Event.cancel(evt); 12214 } 12215 }); 12216 } 12217 12218 Event.add(t.id + '_open', 'click', function (evt) { 12219 t.showMenu(); 12220 Event.cancel(evt); 12221 }); 12222 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12223 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12224 12225 // Old IE doesn't have hover on all elements 12226 if (tinymce.isIE6 || !DOM.boxModel) { 12227 Event.add(t.id, 'mouseover', function() { 12228 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12229 DOM.addClass(t.id, 'mceSplitButtonHover'); 12230 }); 12231 12232 Event.add(t.id, 'mouseout', function() { 12233 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12234 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12235 }); 12236 } 12237 }, 12238 12239 destroy : function() { 12240 this.parent(); 12241 12242 Event.clear(this.id + '_action'); 12243 Event.clear(this.id + '_open'); 12244 Event.clear(this.id); 12245 } 12246 }); 12247 })(tinymce); 12248 12249 (function(tinymce) { 12250 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12251 12252 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12253 ColorSplitButton : function(id, s, ed) { 12254 var t = this; 12255 12256 t.parent(id, s, ed); 12257 12258 t.settings = s = tinymce.extend({ 12259 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', 12260 grid_width : 8, 12261 default_color : '#888888' 12262 }, t.settings); 12263 12264 t.onShowMenu = new tinymce.util.Dispatcher(t); 12265 12266 t.onHideMenu = new tinymce.util.Dispatcher(t); 12267 12268 t.value = s.default_color; 12269 }, 12270 12271 showMenu : function() { 12272 var t = this, r, p, e, p2; 12273 12274 if (t.isDisabled()) 12275 return; 12276 12277 if (!t.isMenuRendered) { 12278 t.renderMenu(); 12279 t.isMenuRendered = true; 12280 } 12281 12282 if (t.isMenuVisible) 12283 return t.hideMenu(); 12284 12285 e = DOM.get(t.id); 12286 DOM.show(t.id + '_menu'); 12287 DOM.addClass(e, 'mceSplitButtonSelected'); 12288 p2 = DOM.getPos(e); 12289 DOM.setStyles(t.id + '_menu', { 12290 left : p2.x, 12291 top : p2.y + e.firstChild.clientHeight, 12292 zIndex : 200000 12293 }); 12294 e = 0; 12295 12296 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12297 t.onShowMenu.dispatch(t); 12298 12299 if (t._focused) { 12300 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12301 if (e.keyCode == 27) 12302 t.hideMenu(); 12303 }); 12304 12305 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12306 } 12307 12308 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12309 root: t.id + '_menu', 12310 items: DOM.select('a', t.id + '_menu'), 12311 onCancel: function() { 12312 t.hideMenu(); 12313 t.focus(); 12314 } 12315 }); 12316 12317 t.keyboardNav.focus(); 12318 t.isMenuVisible = 1; 12319 }, 12320 12321 hideMenu : function(e) { 12322 var t = this; 12323 12324 if (t.isMenuVisible) { 12325 // Prevent double toogles by canceling the mouse click event to the button 12326 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12327 return; 12328 12329 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12330 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12331 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12332 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12333 DOM.hide(t.id + '_menu'); 12334 } 12335 12336 t.isMenuVisible = 0; 12337 t.onHideMenu.dispatch(); 12338 t.keyboardNav.destroy(); 12339 } 12340 }, 12341 12342 renderMenu : function() { 12343 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12344 12345 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); 12346 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12347 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12348 12349 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12350 tb = DOM.add(n, 'tbody'); 12351 12352 // Generate color grid 12353 i = 0; 12354 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12355 c = c.replace(/^#/, ''); 12356 12357 if (!i--) { 12358 tr = DOM.add(tb, 'tr'); 12359 i = s.grid_width - 1; 12360 } 12361 12362 n = DOM.add(tr, 'td'); 12363 var settings = { 12364 href : 'javascript:;', 12365 style : { 12366 backgroundColor : '#' + c 12367 }, 12368 'title': t.editor.getLang('colors.' + c, c), 12369 'data-mce-color' : '#' + c 12370 }; 12371 12372 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12373 if (!tinymce.isIE ) { 12374 settings.role = 'option'; 12375 } 12376 12377 n = DOM.add(n, 'a', settings); 12378 12379 if (t.editor.forcedHighContrastMode) { 12380 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12381 if (n.getContext && (context = n.getContext("2d"))) { 12382 context.fillStyle = '#' + c; 12383 context.fillRect(0, 0, 16, 16); 12384 } else { 12385 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12386 DOM.remove(n); 12387 } 12388 } 12389 }); 12390 12391 if (s.more_colors_func) { 12392 n = DOM.add(tb, 'tr'); 12393 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12394 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12395 12396 Event.add(n, 'click', function(e) { 12397 s.more_colors_func.call(s.more_colors_scope || this); 12398 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12399 }); 12400 } 12401 12402 DOM.addClass(m, 'mceColorSplitMenu'); 12403 12404 // Prevent IE from scrolling and hindering click to occur #4019 12405 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12406 12407 Event.add(t.id + '_menu', 'click', function(e) { 12408 var c; 12409 12410 e = DOM.getParent(e.target, 'a', tb); 12411 12412 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12413 t.setColor(c); 12414 12415 return false; // Prevent IE auto save warning 12416 }); 12417 12418 return w; 12419 }, 12420 12421 setColor : function(c) { 12422 this.displayColor(c); 12423 this.hideMenu(); 12424 this.settings.onselect(c); 12425 }, 12426 12427 displayColor : function(c) { 12428 var t = this; 12429 12430 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12431 12432 t.value = c; 12433 }, 12434 12435 postRender : function() { 12436 var t = this, id = t.id; 12437 12438 t.parent(); 12439 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12440 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12441 }, 12442 12443 destroy : function() { 12444 var self = this; 12445 12446 self.parent(); 12447 12448 Event.clear(self.id + '_menu'); 12449 Event.clear(self.id + '_more'); 12450 DOM.remove(self.id + '_menu'); 12451 12452 if (self.keyboardNav) { 12453 self.keyboardNav.destroy(); 12454 } 12455 } 12456 }); 12457 })(tinymce); 12458 12459 (function(tinymce) { 12460 // Shorten class names 12461 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12462 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12463 renderHTML : function() { 12464 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12465 12466 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12467 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12468 h.push("<span role='application'>"); 12469 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12470 each(controls, function(toolbar) { 12471 h.push(toolbar.renderHTML()); 12472 }); 12473 h.push("</span>"); 12474 h.push('</div>'); 12475 12476 return h.join(''); 12477 }, 12478 12479 focus : function() { 12480 var t = this; 12481 dom.get(t.id).focus(); 12482 }, 12483 12484 postRender : function() { 12485 var t = this, items = []; 12486 12487 each(t.controls, function(toolbar) { 12488 each (toolbar.controls, function(control) { 12489 if (control.id) { 12490 items.push(control); 12491 } 12492 }); 12493 }); 12494 12495 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12496 root: t.id, 12497 items: items, 12498 onCancel: function() { 12499 //Move focus if webkit so that navigation back will read the item. 12500 if (tinymce.isWebKit) { 12501 dom.get(t.editor.id+"_ifr").focus(); 12502 } 12503 t.editor.focus(); 12504 }, 12505 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12506 }); 12507 }, 12508 12509 destroy : function() { 12510 var self = this; 12511 12512 self.parent(); 12513 self.keyNav.destroy(); 12514 Event.clear(self.id); 12515 } 12516 }); 12517 })(tinymce); 12518 12519 (function(tinymce) { 12520 // Shorten class names 12521 var dom = tinymce.DOM, each = tinymce.each; 12522 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12523 renderHTML : function() { 12524 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12525 12526 cl = t.controls; 12527 for (i=0; i<cl.length; i++) { 12528 // Get current control, prev control, next control and if the control is a list box or not 12529 co = cl[i]; 12530 pr = cl[i - 1]; 12531 nx = cl[i + 1]; 12532 12533 // Add toolbar start 12534 if (i === 0) { 12535 c = 'mceToolbarStart'; 12536 12537 if (co.Button) 12538 c += ' mceToolbarStartButton'; 12539 else if (co.SplitButton) 12540 c += ' mceToolbarStartSplitButton'; 12541 else if (co.ListBox) 12542 c += ' mceToolbarStartListBox'; 12543 12544 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12545 } 12546 12547 // Add toolbar end before list box and after the previous button 12548 // This is to fix the o2k7 editor skins 12549 if (pr && co.ListBox) { 12550 if (pr.Button || pr.SplitButton) 12551 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12552 } 12553 12554 // Render control HTML 12555 12556 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12557 if (dom.stdMode) 12558 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12559 else 12560 h += '<td>' + co.renderHTML() + '</td>'; 12561 12562 // Add toolbar start after list box and before the next button 12563 // This is to fix the o2k7 editor skins 12564 if (nx && co.ListBox) { 12565 if (nx.Button || nx.SplitButton) 12566 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12567 } 12568 } 12569 12570 c = 'mceToolbarEnd'; 12571 12572 if (co.Button) 12573 c += ' mceToolbarEndButton'; 12574 else if (co.SplitButton) 12575 c += ' mceToolbarEndSplitButton'; 12576 else if (co.ListBox) 12577 c += ' mceToolbarEndListBox'; 12578 12579 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12580 12581 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>'); 12582 } 12583 }); 12584 })(tinymce); 12585 12586 (function(tinymce) { 12587 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12588 12589 tinymce.create('tinymce.AddOnManager', { 12590 AddOnManager : function() { 12591 var self = this; 12592 12593 self.items = []; 12594 self.urls = {}; 12595 self.lookup = {}; 12596 self.onAdd = new Dispatcher(self); 12597 }, 12598 12599 get : function(n) { 12600 if (this.lookup[n]) { 12601 return this.lookup[n].instance; 12602 } else { 12603 return undefined; 12604 } 12605 }, 12606 12607 dependencies : function(n) { 12608 var result; 12609 if (this.lookup[n]) { 12610 result = this.lookup[n].dependencies; 12611 } 12612 return result || []; 12613 }, 12614 12615 requireLangPack : function(n) { 12616 var s = tinymce.settings; 12617 12618 if (s && s.language && s.language_load !== false) 12619 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12620 }, 12621 12622 add : function(id, o, dependencies) { 12623 this.items.push(o); 12624 this.lookup[id] = {instance:o, dependencies:dependencies}; 12625 this.onAdd.dispatch(this, id, o); 12626 12627 return o; 12628 }, 12629 createUrl: function(baseUrl, dep) { 12630 if (typeof dep === "object") { 12631 return dep 12632 } else { 12633 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12634 } 12635 }, 12636 12637 addComponents: function(pluginName, scripts) { 12638 var pluginUrl = this.urls[pluginName]; 12639 tinymce.each(scripts, function(script){ 12640 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12641 }); 12642 }, 12643 12644 load : function(n, u, cb, s) { 12645 var t = this, url = u; 12646 12647 function loadDependencies() { 12648 var dependencies = t.dependencies(n); 12649 tinymce.each(dependencies, function(dep) { 12650 var newUrl = t.createUrl(u, dep); 12651 t.load(newUrl.resource, newUrl, undefined, undefined); 12652 }); 12653 if (cb) { 12654 if (s) { 12655 cb.call(s); 12656 } else { 12657 cb.call(tinymce.ScriptLoader); 12658 } 12659 } 12660 } 12661 12662 if (t.urls[n]) 12663 return; 12664 if (typeof u === "object") 12665 url = u.prefix + u.resource + u.suffix; 12666 12667 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 12668 url = tinymce.baseURL + '/' + url; 12669 12670 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 12671 12672 if (t.lookup[n]) { 12673 loadDependencies(); 12674 } else { 12675 tinymce.ScriptLoader.add(url, loadDependencies, s); 12676 } 12677 } 12678 }); 12679 12680 // Create plugin and theme managers 12681 tinymce.PluginManager = new tinymce.AddOnManager(); 12682 tinymce.ThemeManager = new tinymce.AddOnManager(); 12683 }(tinymce)); 12684 12685 (function(tinymce) { 12686 // Shorten names 12687 var each = tinymce.each, extend = tinymce.extend, 12688 DOM = tinymce.DOM, Event = tinymce.dom.Event, 12689 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 12690 explode = tinymce.explode, 12691 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 12692 12693 // Setup some URLs where the editor API is located and where the document is 12694 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 12695 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 12696 tinymce.documentBaseURL += '/'; 12697 12698 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 12699 12700 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 12701 12702 // Add before unload listener 12703 // This was required since IE was leaking memory if you added and removed beforeunload listeners 12704 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 12705 tinymce.onBeforeUnload = new Dispatcher(tinymce); 12706 12707 // Must be on window or IE will leak if the editor is placed in frame or iframe 12708 Event.add(window, 'beforeunload', function(e) { 12709 tinymce.onBeforeUnload.dispatch(tinymce, e); 12710 }); 12711 12712 tinymce.onAddEditor = new Dispatcher(tinymce); 12713 12714 tinymce.onRemoveEditor = new Dispatcher(tinymce); 12715 12716 tinymce.EditorManager = extend(tinymce, { 12717 editors : [], 12718 12719 i18n : {}, 12720 12721 activeEditor : null, 12722 12723 init : function(s) { 12724 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 12725 12726 function createId(elm) { 12727 var id = elm.id; 12728 12729 // Use element id, or unique name or generate a unique id 12730 if (!id) { 12731 id = elm.name; 12732 12733 if (id && !DOM.get(id)) { 12734 id = elm.name; 12735 } else { 12736 // Generate unique name 12737 id = DOM.uniqueId(); 12738 } 12739 12740 elm.setAttribute('id', id); 12741 } 12742 12743 return id; 12744 }; 12745 12746 function execCallback(se, n, s) { 12747 var f = se[n]; 12748 12749 if (!f) 12750 return; 12751 12752 if (tinymce.is(f, 'string')) { 12753 s = f.replace(/\.\w+$/, ''); 12754 s = s ? tinymce.resolve(s) : 0; 12755 f = tinymce.resolve(f); 12756 } 12757 12758 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 12759 }; 12760 12761 function hasClass(n, c) { 12762 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 12763 }; 12764 12765 t.settings = s; 12766 12767 // Legacy call 12768 Event.bind(window, 'ready', function() { 12769 var l, co; 12770 12771 execCallback(s, 'onpageload'); 12772 12773 switch (s.mode) { 12774 case "exact": 12775 l = s.elements || ''; 12776 12777 if(l.length > 0) { 12778 each(explode(l), function(v) { 12779 if (DOM.get(v)) { 12780 ed = new tinymce.Editor(v, s); 12781 el.push(ed); 12782 ed.render(1); 12783 } else { 12784 each(document.forms, function(f) { 12785 each(f.elements, function(e) { 12786 if (e.name === v) { 12787 v = 'mce_editor_' + instanceCounter++; 12788 DOM.setAttrib(e, 'id', v); 12789 12790 ed = new tinymce.Editor(v, s); 12791 el.push(ed); 12792 ed.render(1); 12793 } 12794 }); 12795 }); 12796 } 12797 }); 12798 } 12799 break; 12800 12801 case "textareas": 12802 case "specific_textareas": 12803 each(DOM.select('textarea'), function(elm) { 12804 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 12805 return; 12806 12807 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 12808 ed = new tinymce.Editor(createId(elm), s); 12809 el.push(ed); 12810 ed.render(1); 12811 } 12812 }); 12813 break; 12814 12815 default: 12816 if (s.types) { 12817 // Process type specific selector 12818 each(s.types, function(type) { 12819 each(DOM.select(type.selector), function(elm) { 12820 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 12821 el.push(editor); 12822 editor.render(1); 12823 }); 12824 }); 12825 } else if (s.selector) { 12826 // Process global selector 12827 each(DOM.select(s.selector), function(elm) { 12828 var editor = new tinymce.Editor(createId(elm), s); 12829 el.push(editor); 12830 editor.render(1); 12831 }); 12832 } 12833 } 12834 12835 // Call onInit when all editors are initialized 12836 if (s.oninit) { 12837 l = co = 0; 12838 12839 each(el, function(ed) { 12840 co++; 12841 12842 if (!ed.initialized) { 12843 // Wait for it 12844 ed.onInit.add(function() { 12845 l++; 12846 12847 // All done 12848 if (l == co) 12849 execCallback(s, 'oninit'); 12850 }); 12851 } else 12852 l++; 12853 12854 // All done 12855 if (l == co) 12856 execCallback(s, 'oninit'); 12857 }); 12858 } 12859 }); 12860 }, 12861 12862 get : function(id) { 12863 if (id === undef) 12864 return this.editors; 12865 12866 return this.editors[id]; 12867 }, 12868 12869 getInstanceById : function(id) { 12870 return this.get(id); 12871 }, 12872 12873 add : function(editor) { 12874 var self = this, editors = self.editors; 12875 12876 // Add named and index editor instance 12877 editors[editor.id] = editor; 12878 editors.push(editor); 12879 12880 self._setActive(editor); 12881 self.onAddEditor.dispatch(self, editor); 12882 12883 12884 return editor; 12885 }, 12886 12887 remove : function(editor) { 12888 var t = this, i, editors = t.editors; 12889 12890 // Not in the collection 12891 if (!editors[editor.id]) 12892 return null; 12893 12894 delete editors[editor.id]; 12895 12896 for (i = 0; i < editors.length; i++) { 12897 if (editors[i] == editor) { 12898 editors.splice(i, 1); 12899 break; 12900 } 12901 } 12902 12903 // Select another editor since the active one was removed 12904 if (t.activeEditor == editor) 12905 t._setActive(editors[0]); 12906 12907 editor.destroy(); 12908 t.onRemoveEditor.dispatch(t, editor); 12909 12910 return editor; 12911 }, 12912 12913 execCommand : function(c, u, v) { 12914 var t = this, ed = t.get(v), w; 12915 12916 function clr() { 12917 ed.destroy(); 12918 w.detachEvent('onunload', clr); 12919 w = w.tinyMCE = w.tinymce = null; // IE leak 12920 }; 12921 12922 // Manager commands 12923 switch (c) { 12924 case "mceFocus": 12925 ed.focus(); 12926 return true; 12927 12928 case "mceAddEditor": 12929 case "mceAddControl": 12930 if (!t.get(v)) 12931 new tinymce.Editor(v, t.settings).render(); 12932 12933 return true; 12934 12935 case "mceAddFrameControl": 12936 w = v.window; 12937 12938 // Add tinyMCE global instance and tinymce namespace to specified window 12939 w.tinyMCE = tinyMCE; 12940 w.tinymce = tinymce; 12941 12942 tinymce.DOM.doc = w.document; 12943 tinymce.DOM.win = w; 12944 12945 ed = new tinymce.Editor(v.element_id, v); 12946 ed.render(); 12947 12948 // Fix IE memory leaks 12949 if (tinymce.isIE) { 12950 w.attachEvent('onunload', clr); 12951 } 12952 12953 v.page_window = null; 12954 12955 return true; 12956 12957 case "mceRemoveEditor": 12958 case "mceRemoveControl": 12959 if (ed) 12960 ed.remove(); 12961 12962 return true; 12963 12964 case 'mceToggleEditor': 12965 if (!ed) { 12966 t.execCommand('mceAddControl', 0, v); 12967 return true; 12968 } 12969 12970 if (ed.isHidden()) 12971 ed.show(); 12972 else 12973 ed.hide(); 12974 12975 return true; 12976 } 12977 12978 // Run command on active editor 12979 if (t.activeEditor) 12980 return t.activeEditor.execCommand(c, u, v); 12981 12982 return false; 12983 }, 12984 12985 execInstanceCommand : function(id, c, u, v) { 12986 var ed = this.get(id); 12987 12988 if (ed) 12989 return ed.execCommand(c, u, v); 12990 12991 return false; 12992 }, 12993 12994 triggerSave : function() { 12995 each(this.editors, function(e) { 12996 e.save(); 12997 }); 12998 }, 12999 13000 addI18n : function(p, o) { 13001 var lo, i18n = this.i18n; 13002 13003 if (!tinymce.is(p, 'string')) { 13004 each(p, function(o, lc) { 13005 each(o, function(o, g) { 13006 each(o, function(o, k) { 13007 if (g === 'common') 13008 i18n[lc + '.' + k] = o; 13009 else 13010 i18n[lc + '.' + g + '.' + k] = o; 13011 }); 13012 }); 13013 }); 13014 } else { 13015 each(o, function(o, k) { 13016 i18n[p + '.' + k] = o; 13017 }); 13018 } 13019 }, 13020 13021 // Private methods 13022 13023 _setActive : function(editor) { 13024 this.selectedInstance = this.activeEditor = editor; 13025 } 13026 }); 13027 })(tinymce); 13028 13029 (function(tinymce) { 13030 // Shorten these names 13031 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13032 each = tinymce.each, isGecko = tinymce.isGecko, 13033 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13034 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13035 explode = tinymce.explode; 13036 13037 tinymce.create('tinymce.Editor', { 13038 Editor : function(id, settings) { 13039 var self = this, TRUE = true; 13040 13041 self.settings = settings = extend({ 13042 id : id, 13043 language : 'en', 13044 theme : 'advanced', 13045 skin : 'default', 13046 delta_width : 0, 13047 delta_height : 0, 13048 popup_css : '', 13049 plugins : '', 13050 document_base_url : tinymce.documentBaseURL, 13051 add_form_submit_trigger : TRUE, 13052 submit_patch : TRUE, 13053 add_unload_trigger : TRUE, 13054 convert_urls : TRUE, 13055 relative_urls : TRUE, 13056 remove_script_host : TRUE, 13057 table_inline_editing : false, 13058 object_resizing : TRUE, 13059 accessibility_focus : TRUE, 13060 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13061 visual : TRUE, 13062 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13063 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13064 apply_source_formatting : TRUE, 13065 directionality : 'ltr', 13066 forced_root_block : 'p', 13067 hidden_input : TRUE, 13068 padd_empty_editor : TRUE, 13069 render_ui : TRUE, 13070 indentation : '30px', 13071 fix_table_elements : TRUE, 13072 inline_styles : TRUE, 13073 convert_fonts_to_spans : TRUE, 13074 indent : 'simple', 13075 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13076 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13077 validate : TRUE, 13078 entity_encoding : 'named', 13079 url_converter : self.convertURL, 13080 url_converter_scope : self, 13081 ie7_compat : TRUE 13082 }, settings); 13083 13084 self.id = self.editorId = id; 13085 13086 self.isNotDirty = false; 13087 13088 self.plugins = {}; 13089 13090 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13091 base_uri : tinyMCE.baseURI 13092 }); 13093 13094 self.baseURI = tinymce.baseURI; 13095 13096 self.contentCSS = []; 13097 13098 self.contentStyles = []; 13099 13100 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13101 self.setupEvents(); 13102 13103 // Internal command handler objects 13104 self.execCommands = {}; 13105 self.queryStateCommands = {}; 13106 self.queryValueCommands = {}; 13107 13108 // Call setup 13109 self.execCallback('setup', self); 13110 }, 13111 13112 render : function(nst) { 13113 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13114 13115 // Page is not loaded yet, wait for it 13116 if (!Event.domLoaded) { 13117 Event.add(window, 'ready', function() { 13118 t.render(); 13119 }); 13120 return; 13121 } 13122 13123 tinyMCE.settings = s; 13124 13125 // Element not found, then skip initialization 13126 if (!t.getElement()) 13127 return; 13128 13129 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13130 // here since the browser says it has contentEditable support but there is no visible caret. 13131 if (tinymce.isIDevice && !tinymce.isIOS5) 13132 return; 13133 13134 // Add hidden input for non input elements inside form elements 13135 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13136 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13137 13138 // Hide target element early to prevent content flashing 13139 if (!s.content_editable) { 13140 t.orgVisibility = t.getElement().style.visibility; 13141 t.getElement().style.visibility = 'hidden'; 13142 } 13143 13144 if (tinymce.WindowManager) 13145 t.windowManager = new tinymce.WindowManager(t); 13146 13147 if (s.encoding == 'xml') { 13148 t.onGetContent.add(function(ed, o) { 13149 if (o.save) 13150 o.content = DOM.encode(o.content); 13151 }); 13152 } 13153 13154 if (s.add_form_submit_trigger) { 13155 t.onSubmit.addToTop(function() { 13156 if (t.initialized) { 13157 t.save(); 13158 t.isNotDirty = 1; 13159 } 13160 }); 13161 } 13162 13163 if (s.add_unload_trigger) { 13164 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13165 if (t.initialized && !t.destroyed && !t.isHidden()) 13166 t.save({format : 'raw', no_events : true}); 13167 }); 13168 } 13169 13170 tinymce.addUnload(t.destroy, t); 13171 13172 if (s.submit_patch) { 13173 t.onBeforeRenderUI.add(function() { 13174 var n = t.getElement().form; 13175 13176 if (!n) 13177 return; 13178 13179 // Already patched 13180 if (n._mceOldSubmit) 13181 return; 13182 13183 // Check page uses id="submit" or name="submit" for it's submit button 13184 if (!n.submit.nodeType && !n.submit.length) { 13185 t.formElement = n; 13186 n._mceOldSubmit = n.submit; 13187 n.submit = function() { 13188 // Save all instances 13189 tinymce.triggerSave(); 13190 t.isNotDirty = 1; 13191 13192 return t.formElement._mceOldSubmit(t.formElement); 13193 }; 13194 } 13195 13196 n = null; 13197 }); 13198 } 13199 13200 // Load scripts 13201 function loadScripts() { 13202 if (s.language && s.language_load !== false) 13203 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13204 13205 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13206 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13207 13208 each(explode(s.plugins), function(p) { 13209 if (p &&!PluginManager.urls[p]) { 13210 if (p.charAt(0) == '-') { 13211 p = p.substr(1, p.length); 13212 var dependencies = PluginManager.dependencies(p); 13213 each(dependencies, function(dep) { 13214 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13215 dep = PluginManager.createUrl(defaultSettings, dep); 13216 PluginManager.load(dep.resource, dep); 13217 }); 13218 } else { 13219 // Skip safari plugin, since it is removed as of 3.3b1 13220 if (p == 'safari') { 13221 return; 13222 } 13223 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13224 } 13225 } 13226 }); 13227 13228 // Init when que is loaded 13229 sl.loadQueue(function() { 13230 if (!t.removed) 13231 t.init(); 13232 }); 13233 }; 13234 13235 loadScripts(); 13236 }, 13237 13238 init : function() { 13239 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13240 13241 tinymce.add(t); 13242 13243 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13244 13245 if (s.theme) { 13246 if (typeof s.theme != "function") { 13247 s.theme = s.theme.replace(/-/, ''); 13248 o = ThemeManager.get(s.theme); 13249 t.theme = new o(); 13250 13251 if (t.theme.init) 13252 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13253 } else { 13254 t.theme = s.theme; 13255 } 13256 } 13257 13258 function initPlugin(p) { 13259 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13260 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13261 each(PluginManager.dependencies(p), function(dep){ 13262 initPlugin(dep); 13263 }); 13264 po = new c(t, u); 13265 13266 t.plugins[p] = po; 13267 13268 if (po.init) { 13269 po.init(t, u); 13270 initializedPlugins.push(p); 13271 } 13272 } 13273 } 13274 13275 // Create all plugins 13276 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13277 13278 // Setup popup CSS path(s) 13279 if (s.popup_css !== false) { 13280 if (s.popup_css) 13281 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13282 else 13283 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13284 } 13285 13286 if (s.popup_css_add) 13287 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13288 13289 t.controlManager = new tinymce.ControlManager(t); 13290 13291 // Enables users to override the control factory 13292 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13293 13294 // Measure box 13295 if (s.render_ui && t.theme) { 13296 t.orgDisplay = e.style.display; 13297 13298 if (typeof s.theme != "function") { 13299 w = s.width || e.style.width || e.offsetWidth; 13300 h = s.height || e.style.height || e.offsetHeight; 13301 mh = s.min_height || 100; 13302 re = /^[0-9\.]+(|px)$/i; 13303 13304 if (re.test('' + w)) 13305 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13306 13307 if (re.test('' + h)) 13308 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13309 13310 // Render UI 13311 o = t.theme.renderUI({ 13312 targetNode : e, 13313 width : w, 13314 height : h, 13315 deltaWidth : s.delta_width, 13316 deltaHeight : s.delta_height 13317 }); 13318 13319 // Resize editor 13320 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13321 width : w, 13322 height : h 13323 }); 13324 13325 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13326 if (h < mh) 13327 h = mh; 13328 } else { 13329 o = s.theme(t, e); 13330 13331 // Convert element type to id:s 13332 if (o.editorContainer.nodeType) { 13333 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13334 } 13335 13336 // Convert element type to id:s 13337 if (o.iframeContainer.nodeType) { 13338 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13339 } 13340 13341 // Use specified iframe height or the targets offsetHeight 13342 h = o.iframeHeight || e.offsetHeight; 13343 13344 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13345 if (isIE) { 13346 t.onInit.add(function(ed) { 13347 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { 13348 ed.lastIERng = ed.selection.getRng(); 13349 }); 13350 }); 13351 } 13352 } 13353 13354 t.editorContainer = o.editorContainer; 13355 } 13356 13357 // Load specified content CSS last 13358 if (s.content_css) { 13359 each(explode(s.content_css), function(u) { 13360 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13361 }); 13362 } 13363 13364 // Content editable mode ends here 13365 if (s.content_editable) { 13366 e = n = o = null; // Fix IE leak 13367 return t.initContentBody(); 13368 } 13369 13370 // User specified a document.domain value 13371 if (document.domain && location.hostname != document.domain) 13372 tinymce.relaxedDomain = document.domain; 13373 13374 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13375 13376 // We only need to override paths if we have to 13377 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13378 if (s.document_base_url != tinymce.documentBaseURL) 13379 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13380 13381 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13382 if (s.ie7_compat) 13383 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13384 else 13385 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13386 13387 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13388 13389 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13390 for (i = 0; i < t.contentCSS.length; i++) { 13391 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13392 } 13393 13394 t.contentCSS = []; 13395 13396 bi = s.body_id || 'tinymce'; 13397 if (bi.indexOf('=') != -1) { 13398 bi = t.getParam('body_id', '', 'hash'); 13399 bi = bi[t.id] || bi; 13400 } 13401 13402 bc = s.body_class || ''; 13403 if (bc.indexOf('=') != -1) { 13404 bc = t.getParam('body_class', '', 'hash'); 13405 bc = bc[t.id] || ''; 13406 } 13407 13408 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13409 13410 // Domain relaxing enabled, then set document domain 13411 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13412 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13413 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13414 } 13415 13416 // Create iframe 13417 // TODO: ACC add the appropriate description on this. 13418 n = DOM.add(o.iframeContainer, 'iframe', { 13419 id : t.id + "_ifr", 13420 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13421 frameBorder : '0', 13422 allowTransparency : "true", 13423 title : s.aria_label, 13424 style : { 13425 width : '100%', 13426 height : h, 13427 display : 'block' // Important for Gecko to render the iframe correctly 13428 } 13429 }); 13430 13431 t.contentAreaContainer = o.iframeContainer; 13432 13433 if (o.editorContainer) { 13434 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13435 } 13436 13437 // Restore visibility on target element 13438 e.style.visibility = t.orgVisibility; 13439 13440 DOM.get(t.id).style.display = 'none'; 13441 DOM.setAttrib(t.id, 'aria-hidden', true); 13442 13443 if (!tinymce.relaxedDomain || !u) 13444 t.initContentBody(); 13445 13446 e = n = o = null; // Cleanup 13447 }, 13448 13449 initContentBody : function() { 13450 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13451 13452 // Setup iframe body 13453 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13454 doc.open(); 13455 doc.write(self.iframeHTML); 13456 doc.close(); 13457 13458 if (tinymce.relaxedDomain) 13459 doc.domain = tinymce.relaxedDomain; 13460 } 13461 13462 if (settings.content_editable) { 13463 DOM.addClass(targetElm, 'mceContentBody'); 13464 self.contentDocument = doc = settings.content_document || document; 13465 self.contentWindow = settings.content_window || window; 13466 self.bodyElement = targetElm; 13467 13468 // Prevent leak in IE 13469 settings.content_document = settings.content_window = null; 13470 } 13471 13472 // It will not steal focus while setting contentEditable 13473 body = self.getBody(); 13474 body.disabled = true; 13475 13476 if (!settings.readonly) 13477 body.contentEditable = self.getParam('content_editable_state', true); 13478 13479 body.disabled = false; 13480 13481 self.schema = new tinymce.html.Schema(settings); 13482 13483 self.dom = new tinymce.dom.DOMUtils(doc, { 13484 keep_values : true, 13485 url_converter : self.convertURL, 13486 url_converter_scope : self, 13487 hex_colors : settings.force_hex_style_colors, 13488 class_filter : settings.class_filter, 13489 update_styles : true, 13490 root_element : settings.content_editable ? self.id : null, 13491 schema : self.schema 13492 }); 13493 13494 self.parser = new tinymce.html.DomParser(settings, self.schema); 13495 13496 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13497 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13498 var i = nodes.length, node, dom = self.dom, value, internalName; 13499 13500 while (i--) { 13501 node = nodes[i]; 13502 value = node.attr(name); 13503 internalName = 'data-mce-' + name; 13504 13505 // Add internal attribute if we need to we don't on a refresh of the document 13506 if (!node.attributes.map[internalName]) { 13507 if (name === "style") 13508 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13509 else 13510 node.attr(internalName, self.convertURL(value, name, node.name)); 13511 } 13512 } 13513 }); 13514 13515 // Keep scripts from executing 13516 self.parser.addNodeFilter('script', function(nodes, name) { 13517 var i = nodes.length, node; 13518 13519 while (i--) { 13520 node = nodes[i]; 13521 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13522 } 13523 }); 13524 13525 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13526 var i = nodes.length, node; 13527 13528 while (i--) { 13529 node = nodes[i]; 13530 node.type = 8; 13531 node.name = '#comment'; 13532 node.value = '[CDATA[' + node.value + ']]'; 13533 } 13534 }); 13535 13536 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13537 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13538 13539 while (i--) { 13540 node = nodes[i]; 13541 13542 if (node.isEmpty(nonEmptyElements)) 13543 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13544 } 13545 }); 13546 13547 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13548 13549 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13550 13551 self.formatter = new tinymce.Formatter(self); 13552 13553 self.undoManager = new tinymce.UndoManager(self); 13554 13555 self.forceBlocks = new tinymce.ForceBlocks(self); 13556 self.enterKey = new tinymce.EnterKey(self); 13557 self.editorCommands = new tinymce.EditorCommands(self); 13558 13559 self.onExecCommand.add(function(editor, command) { 13560 // Don't refresh the select lists until caret move 13561 if (!/^(FontName|FontSize)$/.test(command)) 13562 self.nodeChanged(); 13563 }); 13564 13565 // Pass through 13566 self.serializer.onPreProcess.add(function(se, o) { 13567 return self.onPreProcess.dispatch(self, o, se); 13568 }); 13569 13570 self.serializer.onPostProcess.add(function(se, o) { 13571 return self.onPostProcess.dispatch(self, o, se); 13572 }); 13573 13574 self.onPreInit.dispatch(self); 13575 13576 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13577 doc.body.spellcheck = false; 13578 13579 if (!settings.readonly) { 13580 self.bindNativeEvents(); 13581 } 13582 13583 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13584 self.onPostRender.dispatch(self); 13585 13586 self.quirks = tinymce.util.Quirks(self); 13587 13588 if (settings.directionality) 13589 body.dir = settings.directionality; 13590 13591 if (settings.nowrap) 13592 body.style.whiteSpace = "nowrap"; 13593 13594 if (settings.protect) { 13595 self.onBeforeSetContent.add(function(ed, o) { 13596 each(settings.protect, function(pattern) { 13597 o.content = o.content.replace(pattern, function(str) { 13598 return '<!--mce:protected ' + escape(str) + '-->'; 13599 }); 13600 }); 13601 }); 13602 } 13603 13604 // Add visual aids when new contents is added 13605 self.onSetContent.add(function() { 13606 self.addVisual(self.getBody()); 13607 }); 13608 13609 // Remove empty contents 13610 if (settings.padd_empty_editor) { 13611 self.onPostProcess.add(function(ed, o) { 13612 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13613 }); 13614 } 13615 13616 self.load({initial : true, format : 'html'}); 13617 self.startContent = self.getContent({format : 'raw'}); 13618 13619 self.initialized = true; 13620 13621 self.onInit.dispatch(self); 13622 self.execCallback('setupcontent_callback', self.id, body, doc); 13623 self.execCallback('init_instance_callback', self); 13624 self.focus(true); 13625 self.nodeChanged({initial : true}); 13626 13627 // Add editor specific CSS styles 13628 if (self.contentStyles.length > 0) { 13629 contentCssText = ''; 13630 13631 each(self.contentStyles, function(style) { 13632 contentCssText += style + "\r\n"; 13633 }); 13634 13635 self.dom.addStyle(contentCssText); 13636 } 13637 13638 // Load specified content CSS last 13639 each(self.contentCSS, function(url) { 13640 self.dom.loadCSS(url); 13641 }); 13642 13643 // Handle auto focus 13644 if (settings.auto_focus) { 13645 setTimeout(function () { 13646 var ed = tinymce.get(settings.auto_focus); 13647 13648 ed.selection.select(ed.getBody(), 1); 13649 ed.selection.collapse(1); 13650 ed.getBody().focus(); 13651 ed.getWin().focus(); 13652 }, 100); 13653 } 13654 13655 // Clean up references for IE 13656 targetElm = doc = body = null; 13657 }, 13658 13659 focus : function(skip_focus) { 13660 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 13661 13662 if (!skip_focus) { 13663 if (self.lastIERng) { 13664 selection.setRng(self.lastIERng); 13665 } 13666 13667 // Get selected control element 13668 ieRng = selection.getRng(); 13669 if (ieRng.item) { 13670 controlElm = ieRng.item(0); 13671 } 13672 13673 self._refreshContentEditable(); 13674 13675 // Focus the window iframe 13676 if (!contentEditable) { 13677 self.getWin().focus(); 13678 } 13679 13680 // Focus the body as well since it's contentEditable 13681 if (tinymce.isGecko || contentEditable) { 13682 body = self.getBody(); 13683 13684 // Check for setActive since it doesn't scroll to the element 13685 if (body.setActive) { 13686 body.setActive(); 13687 } else { 13688 body.focus(); 13689 } 13690 13691 if (contentEditable) { 13692 selection.normalize(); 13693 } 13694 } 13695 13696 // Restore selected control element 13697 // This is needed when for example an image is selected within a 13698 // layer a call to focus will then remove the control selection 13699 if (controlElm && controlElm.ownerDocument == doc) { 13700 ieRng = doc.body.createControlRange(); 13701 ieRng.addElement(controlElm); 13702 ieRng.select(); 13703 } 13704 } 13705 13706 if (tinymce.activeEditor != self) { 13707 if ((oed = tinymce.activeEditor) != null) 13708 oed.onDeactivate.dispatch(oed, self); 13709 13710 self.onActivate.dispatch(self, oed); 13711 } 13712 13713 tinymce._setActive(self); 13714 }, 13715 13716 execCallback : function(n) { 13717 var t = this, f = t.settings[n], s; 13718 13719 if (!f) 13720 return; 13721 13722 // Look through lookup 13723 if (t.callbackLookup && (s = t.callbackLookup[n])) { 13724 f = s.func; 13725 s = s.scope; 13726 } 13727 13728 if (is(f, 'string')) { 13729 s = f.replace(/\.\w+$/, ''); 13730 s = s ? tinymce.resolve(s) : 0; 13731 f = tinymce.resolve(f); 13732 t.callbackLookup = t.callbackLookup || {}; 13733 t.callbackLookup[n] = {func : f, scope : s}; 13734 } 13735 13736 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 13737 }, 13738 13739 translate : function(s) { 13740 var c = this.settings.language || 'en', i18n = tinymce.i18n; 13741 13742 if (!s) 13743 return ''; 13744 13745 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 13746 return i18n[c + '.' + b] || '{#' + b + '}'; 13747 }); 13748 }, 13749 13750 getLang : function(n, dv) { 13751 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 13752 }, 13753 13754 getParam : function(n, dv, ty) { 13755 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 13756 13757 if (ty === 'hash') { 13758 o = {}; 13759 13760 if (is(v, 'string')) { 13761 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 13762 v = v.split('='); 13763 13764 if (v.length > 1) 13765 o[tr(v[0])] = tr(v[1]); 13766 else 13767 o[tr(v[0])] = tr(v); 13768 }); 13769 } else 13770 o = v; 13771 13772 return o; 13773 } 13774 13775 return v; 13776 }, 13777 13778 nodeChanged : function(o) { 13779 var self = this, selection = self.selection, node; 13780 13781 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 13782 if (self.initialized) { 13783 o = o || {}; 13784 13785 // Get start node 13786 node = selection.getStart() || self.getBody(); 13787 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 13788 13789 // Get parents and add them to object 13790 o.parents = []; 13791 self.dom.getParent(node, function(node) { 13792 if (node.nodeName == 'BODY') 13793 return true; 13794 13795 o.parents.push(node); 13796 }); 13797 13798 self.onNodeChange.dispatch( 13799 self, 13800 o ? o.controlManager || self.controlManager : self.controlManager, 13801 node, 13802 selection.isCollapsed(), 13803 o 13804 ); 13805 } 13806 }, 13807 13808 addButton : function(name, settings) { 13809 var self = this; 13810 13811 self.buttons = self.buttons || {}; 13812 self.buttons[name] = settings; 13813 }, 13814 13815 addCommand : function(name, callback, scope) { 13816 this.execCommands[name] = {func : callback, scope : scope || this}; 13817 }, 13818 13819 addQueryStateHandler : function(name, callback, scope) { 13820 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 13821 }, 13822 13823 addQueryValueHandler : function(name, callback, scope) { 13824 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 13825 }, 13826 13827 addShortcut : function(pa, desc, cmd_func, sc) { 13828 var t = this, c; 13829 13830 if (t.settings.custom_shortcuts === false) 13831 return false; 13832 13833 t.shortcuts = t.shortcuts || {}; 13834 13835 if (is(cmd_func, 'string')) { 13836 c = cmd_func; 13837 13838 cmd_func = function() { 13839 t.execCommand(c, false, null); 13840 }; 13841 } 13842 13843 if (is(cmd_func, 'object')) { 13844 c = cmd_func; 13845 13846 cmd_func = function() { 13847 t.execCommand(c[0], c[1], c[2]); 13848 }; 13849 } 13850 13851 each(explode(pa), function(pa) { 13852 var o = { 13853 func : cmd_func, 13854 scope : sc || this, 13855 desc : t.translate(desc), 13856 alt : false, 13857 ctrl : false, 13858 shift : false 13859 }; 13860 13861 each(explode(pa, '+'), function(v) { 13862 switch (v) { 13863 case 'alt': 13864 case 'ctrl': 13865 case 'shift': 13866 o[v] = true; 13867 break; 13868 13869 default: 13870 o.charCode = v.charCodeAt(0); 13871 o.keyCode = v.toUpperCase().charCodeAt(0); 13872 } 13873 }); 13874 13875 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 13876 }); 13877 13878 return true; 13879 }, 13880 13881 execCommand : function(cmd, ui, val, a) { 13882 var t = this, s = 0, o, st; 13883 13884 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 13885 t.focus(); 13886 13887 a = extend({}, a); 13888 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 13889 if (a.terminate) 13890 return false; 13891 13892 // Command callback 13893 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 13894 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13895 return true; 13896 } 13897 13898 // Registred commands 13899 if (o = t.execCommands[cmd]) { 13900 st = o.func.call(o.scope, ui, val); 13901 13902 // Fall through on true 13903 if (st !== true) { 13904 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13905 return st; 13906 } 13907 } 13908 13909 // Plugin commands 13910 each(t.plugins, function(p) { 13911 if (p.execCommand && p.execCommand(cmd, ui, val)) { 13912 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13913 s = 1; 13914 return false; 13915 } 13916 }); 13917 13918 if (s) 13919 return true; 13920 13921 // Theme commands 13922 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 13923 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13924 return true; 13925 } 13926 13927 // Editor commands 13928 if (t.editorCommands.execCommand(cmd, ui, val)) { 13929 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13930 return true; 13931 } 13932 13933 // Browser commands 13934 t.getDoc().execCommand(cmd, ui, val); 13935 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13936 }, 13937 13938 queryCommandState : function(cmd) { 13939 var t = this, o, s; 13940 13941 // Is hidden then return undefined 13942 if (t._isHidden()) 13943 return; 13944 13945 // Registred commands 13946 if (o = t.queryStateCommands[cmd]) { 13947 s = o.func.call(o.scope); 13948 13949 // Fall though on true 13950 if (s !== true) 13951 return s; 13952 } 13953 13954 // Registred commands 13955 o = t.editorCommands.queryCommandState(cmd); 13956 if (o !== -1) 13957 return o; 13958 13959 // Browser commands 13960 try { 13961 return this.getDoc().queryCommandState(cmd); 13962 } catch (ex) { 13963 // Fails sometimes see bug: 1896577 13964 } 13965 }, 13966 13967 queryCommandValue : function(c) { 13968 var t = this, o, s; 13969 13970 // Is hidden then return undefined 13971 if (t._isHidden()) 13972 return; 13973 13974 // Registred commands 13975 if (o = t.queryValueCommands[c]) { 13976 s = o.func.call(o.scope); 13977 13978 // Fall though on true 13979 if (s !== true) 13980 return s; 13981 } 13982 13983 // Registred commands 13984 o = t.editorCommands.queryCommandValue(c); 13985 if (is(o)) 13986 return o; 13987 13988 // Browser commands 13989 try { 13990 return this.getDoc().queryCommandValue(c); 13991 } catch (ex) { 13992 // Fails sometimes see bug: 1896577 13993 } 13994 }, 13995 13996 show : function() { 13997 var self = this; 13998 13999 DOM.show(self.getContainer()); 14000 DOM.hide(self.id); 14001 self.load(); 14002 }, 14003 14004 hide : function() { 14005 var self = this, doc = self.getDoc(); 14006 14007 // Fixed bug where IE has a blinking cursor left from the editor 14008 if (isIE && doc) 14009 doc.execCommand('SelectAll'); 14010 14011 // We must save before we hide so Safari doesn't crash 14012 self.save(); 14013 DOM.hide(self.getContainer()); 14014 DOM.setStyle(self.id, 'display', self.orgDisplay); 14015 }, 14016 14017 isHidden : function() { 14018 return !DOM.isHidden(this.id); 14019 }, 14020 14021 setProgressState : function(b, ti, o) { 14022 this.onSetProgressState.dispatch(this, b, ti, o); 14023 14024 return b; 14025 }, 14026 14027 load : function(o) { 14028 var t = this, e = t.getElement(), h; 14029 14030 if (e) { 14031 o = o || {}; 14032 o.load = true; 14033 14034 // Double encode existing entities in the value 14035 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14036 o.element = e; 14037 14038 if (!o.no_events) 14039 t.onLoadContent.dispatch(t, o); 14040 14041 o.element = e = null; 14042 14043 return h; 14044 } 14045 }, 14046 14047 save : function(o) { 14048 var t = this, e = t.getElement(), h, f; 14049 14050 if (!e || !t.initialized) 14051 return; 14052 14053 o = o || {}; 14054 o.save = true; 14055 14056 o.element = e; 14057 h = o.content = t.getContent(o); 14058 14059 if (!o.no_events) 14060 t.onSaveContent.dispatch(t, o); 14061 14062 h = o.content; 14063 14064 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14065 e.innerHTML = h; 14066 14067 // Update hidden form element 14068 if (f = DOM.getParent(t.id, 'form')) { 14069 each(f.elements, function(e) { 14070 if (e.name == t.id) { 14071 e.value = h; 14072 return false; 14073 } 14074 }); 14075 } 14076 } else 14077 e.value = h; 14078 14079 o.element = e = null; 14080 14081 return h; 14082 }, 14083 14084 setContent : function(content, args) { 14085 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14086 14087 // Setup args object 14088 args = args || {}; 14089 args.format = args.format || 'html'; 14090 args.set = true; 14091 args.content = content; 14092 14093 // Do preprocessing 14094 if (!args.no_events) 14095 self.onBeforeSetContent.dispatch(self, args); 14096 14097 content = args.content; 14098 14099 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14100 // It will also be impossible to place the caret in the editor unless there is a BR element present 14101 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14102 forcedRootBlockName = self.settings.forced_root_block; 14103 if (forcedRootBlockName) 14104 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14105 else 14106 content = '<br data-mce-bogus="1">'; 14107 14108 body.innerHTML = content; 14109 self.selection.select(body, true); 14110 self.selection.collapse(true); 14111 return; 14112 } 14113 14114 // Parse and serialize the html 14115 if (args.format !== 'raw') { 14116 content = new tinymce.html.Serializer({}, self.schema).serialize( 14117 self.parser.parse(content) 14118 ); 14119 } 14120 14121 // Set the new cleaned contents to the editor 14122 args.content = tinymce.trim(content); 14123 self.dom.setHTML(body, args.content); 14124 14125 // Do post processing 14126 if (!args.no_events) 14127 self.onSetContent.dispatch(self, args); 14128 14129 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14130 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14131 self.selection.normalize(); 14132 } 14133 14134 return args.content; 14135 }, 14136 14137 getContent : function(args) { 14138 var self = this, content; 14139 14140 // Setup args object 14141 args = args || {}; 14142 args.format = args.format || 'html'; 14143 args.get = true; 14144 args.getInner = true; 14145 14146 // Do preprocessing 14147 if (!args.no_events) 14148 self.onBeforeGetContent.dispatch(self, args); 14149 14150 // Get raw contents or by default the cleaned contents 14151 if (args.format == 'raw') 14152 content = self.getBody().innerHTML; 14153 else 14154 content = self.serializer.serialize(self.getBody(), args); 14155 14156 args.content = tinymce.trim(content); 14157 14158 // Do post processing 14159 if (!args.no_events) 14160 self.onGetContent.dispatch(self, args); 14161 14162 return args.content; 14163 }, 14164 14165 isDirty : function() { 14166 var self = this; 14167 14168 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14169 }, 14170 14171 getContainer : function() { 14172 var self = this; 14173 14174 if (!self.container) 14175 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14176 14177 return self.container; 14178 }, 14179 14180 getContentAreaContainer : function() { 14181 return this.contentAreaContainer; 14182 }, 14183 14184 getElement : function() { 14185 return DOM.get(this.settings.content_element || this.id); 14186 }, 14187 14188 getWin : function() { 14189 var self = this, elm; 14190 14191 if (!self.contentWindow) { 14192 elm = DOM.get(self.id + "_ifr"); 14193 14194 if (elm) 14195 self.contentWindow = elm.contentWindow; 14196 } 14197 14198 return self.contentWindow; 14199 }, 14200 14201 getDoc : function() { 14202 var self = this, win; 14203 14204 if (!self.contentDocument) { 14205 win = self.getWin(); 14206 14207 if (win) 14208 self.contentDocument = win.document; 14209 } 14210 14211 return self.contentDocument; 14212 }, 14213 14214 getBody : function() { 14215 return this.bodyElement || this.getDoc().body; 14216 }, 14217 14218 convertURL : function(url, name, elm) { 14219 var self = this, settings = self.settings; 14220 14221 // Use callback instead 14222 if (settings.urlconverter_callback) 14223 return self.execCallback('urlconverter_callback', url, elm, true, name); 14224 14225 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14226 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14227 return url; 14228 14229 // Convert to relative 14230 if (settings.relative_urls) 14231 return self.documentBaseURI.toRelative(url); 14232 14233 // Convert to absolute 14234 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14235 14236 return url; 14237 }, 14238 14239 addVisual : function(elm) { 14240 var self = this, settings = self.settings, dom = self.dom, cls; 14241 14242 elm = elm || self.getBody(); 14243 14244 if (!is(self.hasVisual)) 14245 self.hasVisual = settings.visual; 14246 14247 each(dom.select('table,a', elm), function(elm) { 14248 var value; 14249 14250 switch (elm.nodeName) { 14251 case 'TABLE': 14252 cls = settings.visual_table_class || 'mceItemTable'; 14253 value = dom.getAttrib(elm, 'border'); 14254 14255 if (!value || value == '0') { 14256 if (self.hasVisual) 14257 dom.addClass(elm, cls); 14258 else 14259 dom.removeClass(elm, cls); 14260 } 14261 14262 return; 14263 14264 case 'A': 14265 if (!dom.getAttrib(elm, 'href', false)) { 14266 value = dom.getAttrib(elm, 'name') || elm.id; 14267 cls = 'mceItemAnchor'; 14268 14269 if (value) { 14270 if (self.hasVisual) 14271 dom.addClass(elm, cls); 14272 else 14273 dom.removeClass(elm, cls); 14274 } 14275 } 14276 14277 return; 14278 } 14279 }); 14280 14281 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14282 }, 14283 14284 remove : function() { 14285 var self = this, elm = self.getContainer(); 14286 14287 if (!self.removed) { 14288 self.removed = 1; // Cancels post remove event execution 14289 self.hide(); 14290 14291 // Don't clear the window or document if content editable 14292 // is enabled since other instances might still be present 14293 if (!self.settings.content_editable) { 14294 Event.unbind(self.getWin()); 14295 Event.unbind(self.getDoc()); 14296 } 14297 14298 Event.unbind(self.getBody()); 14299 Event.clear(elm); 14300 14301 self.execCallback('remove_instance_callback', self); 14302 self.onRemove.dispatch(self); 14303 14304 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14305 self.onExecCommand.listeners = []; 14306 14307 tinymce.remove(self); 14308 DOM.remove(elm); 14309 } 14310 }, 14311 14312 destroy : function(s) { 14313 var t = this; 14314 14315 // One time is enough 14316 if (t.destroyed) 14317 return; 14318 14319 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14320 if (isGecko) { 14321 Event.unbind(t.getDoc()); 14322 Event.unbind(t.getWin()); 14323 Event.unbind(t.getBody()); 14324 } 14325 14326 if (!s) { 14327 tinymce.removeUnload(t.destroy); 14328 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14329 14330 // Manual destroy 14331 if (t.theme && t.theme.destroy) 14332 t.theme.destroy(); 14333 14334 // Destroy controls, selection and dom 14335 t.controlManager.destroy(); 14336 t.selection.destroy(); 14337 t.dom.destroy(); 14338 } 14339 14340 if (t.formElement) { 14341 t.formElement.submit = t.formElement._mceOldSubmit; 14342 t.formElement._mceOldSubmit = null; 14343 } 14344 14345 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14346 14347 if (t.selection) 14348 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14349 14350 t.destroyed = 1; 14351 }, 14352 14353 // Internal functions 14354 14355 _refreshContentEditable : function() { 14356 var self = this, body, parent; 14357 14358 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14359 if (self._isHidden()) { 14360 body = self.getBody(); 14361 parent = body.parentNode; 14362 14363 parent.removeChild(body); 14364 parent.appendChild(body); 14365 14366 body.focus(); 14367 } 14368 }, 14369 14370 _isHidden : function() { 14371 var s; 14372 14373 if (!isGecko) 14374 return 0; 14375 14376 // Weird, wheres that cursor selection? 14377 s = this.selection.getSel(); 14378 return (!s || !s.rangeCount || s.rangeCount === 0); 14379 } 14380 }); 14381 })(tinymce); 14382 (function(tinymce) { 14383 var each = tinymce.each; 14384 14385 tinymce.Editor.prototype.setupEvents = function() { 14386 var self = this, settings = self.settings; 14387 14388 // Add events to the editor 14389 each([ 14390 'onPreInit', 14391 14392 'onBeforeRenderUI', 14393 14394 'onPostRender', 14395 14396 'onLoad', 14397 14398 'onInit', 14399 14400 'onRemove', 14401 14402 'onActivate', 14403 14404 'onDeactivate', 14405 14406 'onClick', 14407 14408 'onEvent', 14409 14410 'onMouseUp', 14411 14412 'onMouseDown', 14413 14414 'onDblClick', 14415 14416 'onKeyDown', 14417 14418 'onKeyUp', 14419 14420 'onKeyPress', 14421 14422 'onContextMenu', 14423 14424 'onSubmit', 14425 14426 'onReset', 14427 14428 'onPaste', 14429 14430 'onPreProcess', 14431 14432 'onPostProcess', 14433 14434 'onBeforeSetContent', 14435 14436 'onBeforeGetContent', 14437 14438 'onSetContent', 14439 14440 'onGetContent', 14441 14442 'onLoadContent', 14443 14444 'onSaveContent', 14445 14446 'onNodeChange', 14447 14448 'onChange', 14449 14450 'onBeforeExecCommand', 14451 14452 'onExecCommand', 14453 14454 'onUndo', 14455 14456 'onRedo', 14457 14458 'onVisualAid', 14459 14460 'onSetProgressState', 14461 14462 'onSetAttrib' 14463 ], function(name) { 14464 self[name] = new tinymce.util.Dispatcher(self); 14465 }); 14466 14467 // Handle legacy cleanup_callback option 14468 if (settings.cleanup_callback) { 14469 self.onBeforeSetContent.add(function(ed, o) { 14470 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14471 }); 14472 14473 self.onPreProcess.add(function(ed, o) { 14474 if (o.set) 14475 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14476 14477 if (o.get) 14478 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14479 }); 14480 14481 self.onPostProcess.add(function(ed, o) { 14482 if (o.set) 14483 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14484 14485 if (o.get) 14486 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14487 }); 14488 } 14489 14490 // Handle legacy save_callback option 14491 if (settings.save_callback) { 14492 self.onGetContent.add(function(ed, o) { 14493 if (o.save) 14494 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14495 }); 14496 } 14497 14498 // Handle legacy handle_event_callback option 14499 if (settings.handle_event_callback) { 14500 self.onEvent.add(function(ed, e, o) { 14501 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14502 e.preventDefault(); 14503 e.stopPropagation(); 14504 } 14505 }); 14506 } 14507 14508 // Handle legacy handle_node_change_callback option 14509 if (settings.handle_node_change_callback) { 14510 self.onNodeChange.add(function(ed, cm, n) { 14511 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14512 }); 14513 } 14514 14515 // Handle legacy save_callback option 14516 if (settings.save_callback) { 14517 self.onSaveContent.add(function(ed, o) { 14518 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14519 14520 if (h) 14521 o.content = h; 14522 }); 14523 } 14524 14525 // Handle legacy onchange_callback option 14526 if (settings.onchange_callback) { 14527 self.onChange.add(function(ed, l) { 14528 ed.execCallback('onchange_callback', ed, l); 14529 }); 14530 } 14531 }; 14532 14533 tinymce.Editor.prototype.bindNativeEvents = function() { 14534 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14535 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14536 14537 nativeToDispatcherMap = { 14538 mouseup : 'onMouseUp', 14539 mousedown : 'onMouseDown', 14540 click : 'onClick', 14541 keyup : 'onKeyUp', 14542 keydown : 'onKeyDown', 14543 keypress : 'onKeyPress', 14544 submit : 'onSubmit', 14545 reset : 'onReset', 14546 contextmenu : 'onContextMenu', 14547 dblclick : 'onDblClick', 14548 paste : 'onPaste' // Doesn't work in all browsers yet 14549 }; 14550 14551 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14552 function eventHandler(evt, args) { 14553 var type = evt.type; 14554 14555 // Don't fire events when it's removed 14556 if (self.removed) 14557 return; 14558 14559 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14560 if (self.onEvent.dispatch(self, evt, args) !== false) { 14561 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14562 } 14563 }; 14564 14565 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14566 function doOperaFocus(e) { 14567 self.focus(true); 14568 }; 14569 14570 function nodeChanged(ed, e) { 14571 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14572 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14573 self.selection.normalize(); 14574 } 14575 14576 self.nodeChanged(); 14577 } 14578 14579 // Add DOM events 14580 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14581 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14582 14583 switch (nativeName) { 14584 case 'contextmenu': 14585 dom.bind(root, nativeName, eventHandler); 14586 break; 14587 14588 case 'paste': 14589 dom.bind(self.getBody(), nativeName, eventHandler); 14590 break; 14591 14592 case 'submit': 14593 case 'reset': 14594 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14595 break; 14596 14597 default: 14598 dom.bind(root, nativeName, eventHandler); 14599 } 14600 }); 14601 14602 // Set the editor as active when focused 14603 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14604 self.focus(true); 14605 }); 14606 14607 if (settings.content_editable && tinymce.isOpera) { 14608 dom.bind(self.getBody(), 'click', doOperaFocus); 14609 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14610 } 14611 14612 // Add node change handler 14613 self.onMouseUp.add(nodeChanged); 14614 14615 self.onKeyUp.add(function(ed, e) { 14616 var keyCode = e.keyCode; 14617 14618 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 14619 nodeChanged(ed, e); 14620 }); 14621 14622 // Add reset handler 14623 self.onReset.add(function() { 14624 self.setContent(self.startContent, {format : 'raw'}); 14625 }); 14626 14627 // Add shortcuts 14628 function handleShortcut(e, execute) { 14629 if (e.altKey || e.ctrlKey || e.metaKey) { 14630 each(self.shortcuts, function(shortcut) { 14631 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 14632 14633 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 14634 return; 14635 14636 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 14637 e.preventDefault(); 14638 14639 if (execute) { 14640 shortcut.func.call(shortcut.scope); 14641 } 14642 14643 return true; 14644 } 14645 }); 14646 } 14647 }; 14648 14649 self.onKeyUp.add(function(ed, e) { 14650 handleShortcut(e); 14651 }); 14652 14653 self.onKeyPress.add(function(ed, e) { 14654 handleShortcut(e); 14655 }); 14656 14657 self.onKeyDown.add(function(ed, e) { 14658 handleShortcut(e, true); 14659 }); 14660 14661 if (tinymce.isOpera) { 14662 self.onClick.add(function(ed, e) { 14663 e.preventDefault(); 14664 }); 14665 } 14666 }; 14667 })(tinymce); 14668 (function(tinymce) { 14669 // Added for compression purposes 14670 var each = tinymce.each, undef, TRUE = true, FALSE = false; 14671 14672 tinymce.EditorCommands = function(editor) { 14673 var dom = editor.dom, 14674 selection = editor.selection, 14675 commands = {state: {}, exec : {}, value : {}}, 14676 settings = editor.settings, 14677 formatter = editor.formatter, 14678 bookmark; 14679 14680 function execCommand(command, ui, value) { 14681 var func; 14682 14683 command = command.toLowerCase(); 14684 if (func = commands.exec[command]) { 14685 func(command, ui, value); 14686 return TRUE; 14687 } 14688 14689 return FALSE; 14690 }; 14691 14692 function queryCommandState(command) { 14693 var func; 14694 14695 command = command.toLowerCase(); 14696 if (func = commands.state[command]) 14697 return func(command); 14698 14699 return -1; 14700 }; 14701 14702 function queryCommandValue(command) { 14703 var func; 14704 14705 command = command.toLowerCase(); 14706 if (func = commands.value[command]) 14707 return func(command); 14708 14709 return FALSE; 14710 }; 14711 14712 function addCommands(command_list, type) { 14713 type = type || 'exec'; 14714 14715 each(command_list, function(callback, command) { 14716 each(command.toLowerCase().split(','), function(command) { 14717 commands[type][command] = callback; 14718 }); 14719 }); 14720 }; 14721 14722 // Expose public methods 14723 tinymce.extend(this, { 14724 execCommand : execCommand, 14725 queryCommandState : queryCommandState, 14726 queryCommandValue : queryCommandValue, 14727 addCommands : addCommands 14728 }); 14729 14730 // Private methods 14731 14732 function execNativeCommand(command, ui, value) { 14733 if (ui === undef) 14734 ui = FALSE; 14735 14736 if (value === undef) 14737 value = null; 14738 14739 return editor.getDoc().execCommand(command, ui, value); 14740 }; 14741 14742 function isFormatMatch(name) { 14743 return formatter.match(name); 14744 }; 14745 14746 function toggleFormat(name, value) { 14747 formatter.toggle(name, value ? {value : value} : undef); 14748 }; 14749 14750 function storeSelection(type) { 14751 bookmark = selection.getBookmark(type); 14752 }; 14753 14754 function restoreSelection() { 14755 selection.moveToBookmark(bookmark); 14756 }; 14757 14758 // Add execCommand overrides 14759 addCommands({ 14760 // Ignore these, added for compatibility 14761 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 14762 14763 // Add undo manager logic 14764 'mceEndUndoLevel,mceAddUndoLevel' : function() { 14765 editor.undoManager.add(); 14766 }, 14767 14768 'Cut,Copy,Paste' : function(command) { 14769 var doc = editor.getDoc(), failed; 14770 14771 // Try executing the native command 14772 try { 14773 execNativeCommand(command); 14774 } catch (ex) { 14775 // Command failed 14776 failed = TRUE; 14777 } 14778 14779 // Present alert message about clipboard access not being available 14780 if (failed || !doc.queryCommandSupported(command)) { 14781 if (tinymce.isGecko) { 14782 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 14783 if (state) 14784 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 14785 }); 14786 } else 14787 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 14788 } 14789 }, 14790 14791 // Override unlink command 14792 unlink : function(command) { 14793 if (selection.isCollapsed()) 14794 selection.select(selection.getNode()); 14795 14796 execNativeCommand(command); 14797 selection.collapse(FALSE); 14798 }, 14799 14800 // Override justify commands to use the text formatter engine 14801 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 14802 var align = command.substring(7); 14803 14804 // Remove all other alignments first 14805 each('left,center,right,full'.split(','), function(name) { 14806 if (align != name) 14807 formatter.remove('align' + name); 14808 }); 14809 14810 toggleFormat('align' + align); 14811 execCommand('mceRepaint'); 14812 }, 14813 14814 // Override list commands to fix WebKit bug 14815 'InsertUnorderedList,InsertOrderedList' : function(command) { 14816 var listElm, listParent; 14817 14818 execNativeCommand(command); 14819 14820 // WebKit produces lists within block elements so we need to split them 14821 // we will replace the native list creation logic to custom logic later on 14822 // TODO: Remove this when the list creation logic is removed 14823 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 14824 if (listElm) { 14825 listParent = listElm.parentNode; 14826 14827 // If list is within a text block then split that block 14828 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 14829 storeSelection(); 14830 dom.split(listParent, listElm); 14831 restoreSelection(); 14832 } 14833 } 14834 }, 14835 14836 // Override commands to use the text formatter engine 14837 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 14838 toggleFormat(command); 14839 }, 14840 14841 // Override commands to use the text formatter engine 14842 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 14843 toggleFormat(command, value); 14844 }, 14845 14846 FontSize : function(command, ui, value) { 14847 var fontClasses, fontSizes; 14848 14849 // Convert font size 1-7 to styles 14850 if (value >= 1 && value <= 7) { 14851 fontSizes = tinymce.explode(settings.font_size_style_values); 14852 fontClasses = tinymce.explode(settings.font_size_classes); 14853 14854 if (fontClasses) 14855 value = fontClasses[value - 1] || value; 14856 else 14857 value = fontSizes[value - 1] || value; 14858 } 14859 14860 toggleFormat(command, value); 14861 }, 14862 14863 RemoveFormat : function(command) { 14864 formatter.remove(command); 14865 }, 14866 14867 mceBlockQuote : function(command) { 14868 toggleFormat('blockquote'); 14869 }, 14870 14871 FormatBlock : function(command, ui, value) { 14872 return toggleFormat(value || 'p'); 14873 }, 14874 14875 mceCleanup : function() { 14876 var bookmark = selection.getBookmark(); 14877 14878 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 14879 14880 selection.moveToBookmark(bookmark); 14881 }, 14882 14883 mceRemoveNode : function(command, ui, value) { 14884 var node = value || selection.getNode(); 14885 14886 // Make sure that the body node isn't removed 14887 if (node != editor.getBody()) { 14888 storeSelection(); 14889 editor.dom.remove(node, TRUE); 14890 restoreSelection(); 14891 } 14892 }, 14893 14894 mceSelectNodeDepth : function(command, ui, value) { 14895 var counter = 0; 14896 14897 dom.getParent(selection.getNode(), function(node) { 14898 if (node.nodeType == 1 && counter++ == value) { 14899 selection.select(node); 14900 return FALSE; 14901 } 14902 }, editor.getBody()); 14903 }, 14904 14905 mceSelectNode : function(command, ui, value) { 14906 selection.select(value); 14907 }, 14908 14909 mceInsertContent : function(command, ui, value) { 14910 var parser, serializer, parentNode, rootNode, fragment, args, 14911 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 14912 14913 //selection.normalize(); 14914 14915 // Setup parser and serializer 14916 parser = editor.parser; 14917 serializer = new tinymce.html.Serializer({}, editor.schema); 14918 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 14919 14920 // Run beforeSetContent handlers on the HTML to be inserted 14921 args = {content: value, format: 'html'}; 14922 selection.onBeforeSetContent.dispatch(selection, args); 14923 value = args.content; 14924 14925 // Add caret at end of contents if it's missing 14926 if (value.indexOf('{$caret}') == -1) 14927 value += '{$caret}'; 14928 14929 // Replace the caret marker with a span bookmark element 14930 value = value.replace(/\{\$caret\}/, bookmarkHtml); 14931 14932 // Insert node maker where we will insert the new HTML and get it's parent 14933 if (!selection.isCollapsed()) 14934 editor.getDoc().execCommand('Delete', false, null); 14935 14936 parentNode = selection.getNode(); 14937 14938 // Parse the fragment within the context of the parent node 14939 args = {context : parentNode.nodeName.toLowerCase()}; 14940 fragment = parser.parse(value, args); 14941 14942 // Move the caret to a more suitable location 14943 node = fragment.lastChild; 14944 if (node.attr('id') == 'mce_marker') { 14945 marker = node; 14946 14947 for (node = node.prev; node; node = node.walk(true)) { 14948 if (node.type == 3 || !dom.isBlock(node.name)) { 14949 node.parent.insert(marker, node, node.name === 'br'); 14950 break; 14951 } 14952 } 14953 } 14954 14955 // If parser says valid we can insert the contents into that parent 14956 if (!args.invalid) { 14957 value = serializer.serialize(fragment); 14958 14959 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 14960 node = parentNode.firstChild; 14961 node2 = parentNode.lastChild; 14962 if (!node || (node === node2 && node.nodeName === 'BR')) 14963 dom.setHTML(parentNode, value); 14964 else 14965 selection.setContent(value); 14966 } else { 14967 // If the fragment was invalid within that context then we need 14968 // to parse and process the parent it's inserted into 14969 14970 // Insert bookmark node and get the parent 14971 selection.setContent(bookmarkHtml); 14972 parentNode = editor.selection.getNode(); 14973 rootNode = editor.getBody(); 14974 14975 // Opera will return the document node when selection is in root 14976 if (parentNode.nodeType == 9) 14977 parentNode = node = rootNode; 14978 else 14979 node = parentNode; 14980 14981 // Find the ancestor just before the root element 14982 while (node !== rootNode) { 14983 parentNode = node; 14984 node = node.parentNode; 14985 } 14986 14987 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 14988 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 14989 value = serializer.serialize( 14990 parser.parse( 14991 // Need to replace by using a function since $ in the contents would otherwise be a problem 14992 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 14993 return serializer.serialize(fragment); 14994 }) 14995 ) 14996 ); 14997 14998 // Set the inner/outer HTML depending on if we are in the root or not 14999 if (parentNode == rootNode) 15000 dom.setHTML(rootNode, value); 15001 else 15002 dom.setOuterHTML(parentNode, value); 15003 } 15004 15005 marker = dom.get('mce_marker'); 15006 15007 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15008 nodeRect = dom.getRect(marker); 15009 viewPortRect = dom.getViewPort(editor.getWin()); 15010 15011 // Check if node is out side the viewport if it is then scroll to it 15012 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15013 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15014 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15015 viewportBodyElement.scrollLeft = nodeRect.x; 15016 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15017 } 15018 15019 // Move selection before marker and remove it 15020 rng = dom.createRng(); 15021 15022 // If previous sibling is a text node set the selection to the end of that node 15023 node = marker.previousSibling; 15024 if (node && node.nodeType == 3) { 15025 rng.setStart(node, node.nodeValue.length); 15026 } else { 15027 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15028 rng.setStartBefore(marker); 15029 rng.setEndBefore(marker); 15030 } 15031 15032 // Remove the marker node and set the new range 15033 dom.remove(marker); 15034 selection.setRng(rng); 15035 15036 // Dispatch after event and add any visual elements needed 15037 selection.onSetContent.dispatch(selection, args); 15038 editor.addVisual(); 15039 }, 15040 15041 mceInsertRawHTML : function(command, ui, value) { 15042 selection.setContent('tiny_mce_marker'); 15043 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15044 }, 15045 15046 mceToggleFormat : function(command, ui, value) { 15047 toggleFormat(value); 15048 }, 15049 15050 mceSetContent : function(command, ui, value) { 15051 editor.setContent(value); 15052 }, 15053 15054 'Indent,Outdent' : function(command) { 15055 var intentValue, indentUnit, value; 15056 15057 // Setup indent level 15058 intentValue = settings.indentation; 15059 indentUnit = /[a-z%]+$/i.exec(intentValue); 15060 intentValue = parseInt(intentValue); 15061 15062 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15063 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15064 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15065 formatter.apply('div'); 15066 } 15067 15068 each(selection.getSelectedBlocks(), function(element) { 15069 if (command == 'outdent') { 15070 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15071 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15072 } else 15073 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15074 }); 15075 } else 15076 execNativeCommand(command); 15077 }, 15078 15079 mceRepaint : function() { 15080 var bookmark; 15081 15082 if (tinymce.isGecko) { 15083 try { 15084 storeSelection(TRUE); 15085 15086 if (selection.getSel()) 15087 selection.getSel().selectAllChildren(editor.getBody()); 15088 15089 selection.collapse(TRUE); 15090 restoreSelection(); 15091 } catch (ex) { 15092 // Ignore 15093 } 15094 } 15095 }, 15096 15097 mceToggleFormat : function(command, ui, value) { 15098 formatter.toggle(value); 15099 }, 15100 15101 InsertHorizontalRule : function() { 15102 editor.execCommand('mceInsertContent', false, '<hr />'); 15103 }, 15104 15105 mceToggleVisualAid : function() { 15106 editor.hasVisual = !editor.hasVisual; 15107 editor.addVisual(); 15108 }, 15109 15110 mceReplaceContent : function(command, ui, value) { 15111 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15112 }, 15113 15114 mceInsertLink : function(command, ui, value) { 15115 var anchor; 15116 15117 if (typeof(value) == 'string') 15118 value = {href : value}; 15119 15120 anchor = dom.getParent(selection.getNode(), 'a'); 15121 15122 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15123 value.href = value.href.replace(' ', '%20'); 15124 15125 // Remove existing links if there could be child links or that the href isn't specified 15126 if (!anchor || !value.href) { 15127 formatter.remove('link'); 15128 } 15129 15130 // Apply new link to selection 15131 if (value.href) { 15132 formatter.apply('link', value, anchor); 15133 } 15134 }, 15135 15136 selectAll : function() { 15137 var root = dom.getRoot(), rng = dom.createRng(); 15138 15139 rng.setStart(root, 0); 15140 rng.setEnd(root, root.childNodes.length); 15141 15142 editor.selection.setRng(rng); 15143 } 15144 }); 15145 15146 // Add queryCommandState overrides 15147 addCommands({ 15148 // Override justify commands 15149 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15150 var name = 'align' + command.substring(7); 15151 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15152 var matches = tinymce.map(nodes, function(node) { 15153 return !!formatter.matchNode(node, name); 15154 }); 15155 return tinymce.inArray(matches, TRUE) !== -1; 15156 }, 15157 15158 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15159 return isFormatMatch(command); 15160 }, 15161 15162 mceBlockQuote : function() { 15163 return isFormatMatch('blockquote'); 15164 }, 15165 15166 Outdent : function() { 15167 var node; 15168 15169 if (settings.inline_styles) { 15170 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15171 return TRUE; 15172 15173 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15174 return TRUE; 15175 } 15176 15177 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15178 }, 15179 15180 'InsertUnorderedList,InsertOrderedList' : function(command) { 15181 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); 15182 } 15183 }, 'state'); 15184 15185 // Add queryCommandValue overrides 15186 addCommands({ 15187 'FontSize,FontName' : function(command) { 15188 var value = 0, parent; 15189 15190 if (parent = dom.getParent(selection.getNode(), 'span')) { 15191 if (command == 'fontsize') 15192 value = parent.style.fontSize; 15193 else 15194 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15195 } 15196 15197 return value; 15198 } 15199 }, 'value'); 15200 15201 // Add undo manager logic 15202 addCommands({ 15203 Undo : function() { 15204 editor.undoManager.undo(); 15205 }, 15206 15207 Redo : function() { 15208 editor.undoManager.redo(); 15209 } 15210 }); 15211 }; 15212 })(tinymce); 15213 15214 (function(tinymce) { 15215 var Dispatcher = tinymce.util.Dispatcher; 15216 15217 tinymce.UndoManager = function(editor) { 15218 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15219 15220 function getContent() { 15221 // Remove whitespace before/after and remove pure bogus nodes 15222 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15223 }; 15224 15225 function addNonTypingUndoLevel() { 15226 self.typing = false; 15227 self.add(); 15228 }; 15229 15230 // Create event instances 15231 onBeforeAdd = new Dispatcher(self); 15232 onAdd = new Dispatcher(self); 15233 onUndo = new Dispatcher(self); 15234 onRedo = new Dispatcher(self); 15235 15236 // Pass though onAdd event from UndoManager to Editor as onChange 15237 onAdd.add(function(undoman, level) { 15238 if (undoman.hasUndo()) 15239 return editor.onChange.dispatch(editor, level, undoman); 15240 }); 15241 15242 // Pass though onUndo event from UndoManager to Editor 15243 onUndo.add(function(undoman, level) { 15244 return editor.onUndo.dispatch(editor, level, undoman); 15245 }); 15246 15247 // Pass though onRedo event from UndoManager to Editor 15248 onRedo.add(function(undoman, level) { 15249 return editor.onRedo.dispatch(editor, level, undoman); 15250 }); 15251 15252 // Add initial undo level when the editor is initialized 15253 editor.onInit.add(function() { 15254 self.add(); 15255 }); 15256 15257 // Get position before an execCommand is processed 15258 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15259 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15260 self.beforeChange(); 15261 } 15262 }); 15263 15264 // Add undo level after an execCommand call was made 15265 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15266 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15267 self.add(); 15268 } 15269 }); 15270 15271 // Add undo level on save contents, drag end and blur/focusout 15272 editor.onSaveContent.add(addNonTypingUndoLevel); 15273 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15274 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { 15275 if (!editor.removed && self.typing) { 15276 addNonTypingUndoLevel(); 15277 } 15278 }); 15279 15280 editor.onKeyUp.add(function(editor, e) { 15281 var keyCode = e.keyCode; 15282 15283 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15284 addNonTypingUndoLevel(); 15285 } 15286 }); 15287 15288 editor.onKeyDown.add(function(editor, e) { 15289 var keyCode = e.keyCode; 15290 15291 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15292 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15293 if (self.typing) { 15294 addNonTypingUndoLevel(); 15295 } 15296 15297 return; 15298 } 15299 15300 // If key isn't shift,ctrl,alt,capslock,metakey 15301 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15302 self.beforeChange(); 15303 self.typing = true; 15304 self.add(); 15305 } 15306 }); 15307 15308 editor.onMouseDown.add(function(editor, e) { 15309 if (self.typing) { 15310 addNonTypingUndoLevel(); 15311 } 15312 }); 15313 15314 // Add keyboard shortcuts for undo/redo keys 15315 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15316 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15317 15318 self = { 15319 // Explose for debugging reasons 15320 data : data, 15321 15322 typing : false, 15323 15324 onBeforeAdd: onBeforeAdd, 15325 15326 onAdd : onAdd, 15327 15328 onUndo : onUndo, 15329 15330 onRedo : onRedo, 15331 15332 beforeChange : function() { 15333 beforeBookmark = editor.selection.getBookmark(2, true); 15334 }, 15335 15336 add : function(level) { 15337 var i, settings = editor.settings, lastLevel; 15338 15339 level = level || {}; 15340 level.content = getContent(); 15341 15342 self.onBeforeAdd.dispatch(self, level); 15343 15344 // Add undo level if needed 15345 lastLevel = data[index]; 15346 if (lastLevel && lastLevel.content == level.content) 15347 return null; 15348 15349 // Set before bookmark on previous level 15350 if (data[index]) 15351 data[index].beforeBookmark = beforeBookmark; 15352 15353 // Time to compress 15354 if (settings.custom_undo_redo_levels) { 15355 if (data.length > settings.custom_undo_redo_levels) { 15356 for (i = 0; i < data.length - 1; i++) 15357 data[i] = data[i + 1]; 15358 15359 data.length--; 15360 index = data.length; 15361 } 15362 } 15363 15364 // Get a non intrusive normalized bookmark 15365 level.bookmark = editor.selection.getBookmark(2, true); 15366 15367 // Crop array if needed 15368 if (index < data.length - 1) 15369 data.length = index + 1; 15370 15371 data.push(level); 15372 index = data.length - 1; 15373 15374 self.onAdd.dispatch(self, level); 15375 editor.isNotDirty = 0; 15376 15377 return level; 15378 }, 15379 15380 undo : function() { 15381 var level, i; 15382 15383 if (self.typing) { 15384 self.add(); 15385 self.typing = false; 15386 } 15387 15388 if (index > 0) { 15389 level = data[--index]; 15390 15391 editor.setContent(level.content, {format : 'raw'}); 15392 editor.selection.moveToBookmark(level.beforeBookmark); 15393 15394 self.onUndo.dispatch(self, level); 15395 } 15396 15397 return level; 15398 }, 15399 15400 redo : function() { 15401 var level; 15402 15403 if (index < data.length - 1) { 15404 level = data[++index]; 15405 15406 editor.setContent(level.content, {format : 'raw'}); 15407 editor.selection.moveToBookmark(level.bookmark); 15408 15409 self.onRedo.dispatch(self, level); 15410 } 15411 15412 return level; 15413 }, 15414 15415 clear : function() { 15416 data = []; 15417 index = 0; 15418 self.typing = false; 15419 }, 15420 15421 hasUndo : function() { 15422 return index > 0 || this.typing; 15423 }, 15424 15425 hasRedo : function() { 15426 return index < data.length - 1 && !this.typing; 15427 } 15428 }; 15429 15430 return self; 15431 }; 15432 })(tinymce); 15433 15434 tinymce.ForceBlocks = function(editor) { 15435 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15436 15437 function addRootBlocks() { 15438 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15439 15440 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15441 return; 15442 15443 // Check if node is wrapped in block 15444 while (node && node != rootNode) { 15445 if (blockElements[node.nodeName]) 15446 return; 15447 15448 node = node.parentNode; 15449 } 15450 15451 // Get current selection 15452 rng = selection.getRng(); 15453 if (rng.setStart) { 15454 startContainer = rng.startContainer; 15455 startOffset = rng.startOffset; 15456 endContainer = rng.endContainer; 15457 endOffset = rng.endOffset; 15458 } else { 15459 // Force control range into text range 15460 if (rng.item) { 15461 node = rng.item(0); 15462 rng = editor.getDoc().body.createTextRange(); 15463 rng.moveToElementText(node); 15464 } 15465 15466 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15467 tmpRng = rng.duplicate(); 15468 tmpRng.collapse(true); 15469 startOffset = tmpRng.move('character', offset) * -1; 15470 15471 if (!tmpRng.collapsed) { 15472 tmpRng = rng.duplicate(); 15473 tmpRng.collapse(false); 15474 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15475 } 15476 } 15477 15478 // Wrap non block elements and text nodes 15479 node = rootNode.firstChild; 15480 while (node) { 15481 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15482 if (!rootBlockNode) { 15483 rootBlockNode = dom.create(settings.forced_root_block); 15484 node.parentNode.insertBefore(rootBlockNode, node); 15485 wrapped = true; 15486 } 15487 15488 tempNode = node; 15489 node = node.nextSibling; 15490 rootBlockNode.appendChild(tempNode); 15491 } else { 15492 rootBlockNode = null; 15493 node = node.nextSibling; 15494 } 15495 } 15496 15497 if (wrapped) { 15498 if (rng.setStart) { 15499 rng.setStart(startContainer, startOffset); 15500 rng.setEnd(endContainer, endOffset); 15501 selection.setRng(rng); 15502 } else { 15503 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15504 if (isInEditorDocument) { 15505 try { 15506 rng = editor.getDoc().body.createTextRange(); 15507 rng.moveToElementText(rootNode); 15508 rng.collapse(true); 15509 rng.moveStart('character', startOffset); 15510 15511 if (endOffset > 0) 15512 rng.moveEnd('character', endOffset); 15513 15514 rng.select(); 15515 } catch (ex) { 15516 // Ignore 15517 } 15518 } 15519 } 15520 15521 editor.nodeChanged(); 15522 } 15523 }; 15524 15525 // Force root blocks 15526 if (settings.forced_root_block) { 15527 editor.onKeyUp.add(addRootBlocks); 15528 editor.onNodeChange.add(addRootBlocks); 15529 } 15530 }; 15531 15532 (function(tinymce) { 15533 // Shorten names 15534 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15535 15536 tinymce.create('tinymce.ControlManager', { 15537 ControlManager : function(ed, s) { 15538 var t = this, i; 15539 15540 s = s || {}; 15541 t.editor = ed; 15542 t.controls = {}; 15543 t.onAdd = new tinymce.util.Dispatcher(t); 15544 t.onPostRender = new tinymce.util.Dispatcher(t); 15545 t.prefix = s.prefix || ed.id + '_'; 15546 t._cls = {}; 15547 15548 t.onPostRender.add(function() { 15549 each(t.controls, function(c) { 15550 c.postRender(); 15551 }); 15552 }); 15553 }, 15554 15555 get : function(id) { 15556 return this.controls[this.prefix + id] || this.controls[id]; 15557 }, 15558 15559 setActive : function(id, s) { 15560 var c = null; 15561 15562 if (c = this.get(id)) 15563 c.setActive(s); 15564 15565 return c; 15566 }, 15567 15568 setDisabled : function(id, s) { 15569 var c = null; 15570 15571 if (c = this.get(id)) 15572 c.setDisabled(s); 15573 15574 return c; 15575 }, 15576 15577 add : function(c) { 15578 var t = this; 15579 15580 if (c) { 15581 t.controls[c.id] = c; 15582 t.onAdd.dispatch(c, t); 15583 } 15584 15585 return c; 15586 }, 15587 15588 createControl : function(name) { 15589 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15590 15591 // Build control factory cache 15592 if (!self.controlFactories) { 15593 self.controlFactories = []; 15594 each(editor.plugins, function(plugin) { 15595 if (plugin.createControl) { 15596 self.controlFactories.push(plugin); 15597 } 15598 }); 15599 } 15600 15601 // Create controls by asking cached factories 15602 factories = self.controlFactories; 15603 for (i = 0, l = factories.length; i < l; i++) { 15604 ctrl = factories[i].createControl(name, self); 15605 15606 if (ctrl) { 15607 return self.add(ctrl); 15608 } 15609 } 15610 15611 // Create sepearator 15612 if (name === "|" || name === "separator") { 15613 return self.createSeparator(); 15614 } 15615 15616 // Create control from button collection 15617 if (editor.buttons && (ctrl = editor.buttons[name])) { 15618 return self.createButton(name, ctrl); 15619 } 15620 15621 return self.add(ctrl); 15622 }, 15623 15624 createDropMenu : function(id, s, cc) { 15625 var t = this, ed = t.editor, c, bm, v, cls; 15626 15627 s = extend({ 15628 'class' : 'mceDropDown', 15629 constrain : ed.settings.constrain_menus 15630 }, s); 15631 15632 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 15633 if (v = ed.getParam('skin_variant')) 15634 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 15635 15636 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 15637 15638 id = t.prefix + id; 15639 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 15640 c = t.controls[id] = new cls(id, s); 15641 c.onAddItem.add(function(c, o) { 15642 var s = o.settings; 15643 15644 s.title = ed.getLang(s.title, s.title); 15645 15646 if (!s.onclick) { 15647 s.onclick = function(v) { 15648 if (s.cmd) 15649 ed.execCommand(s.cmd, s.ui || false, s.value); 15650 }; 15651 } 15652 }); 15653 15654 ed.onRemove.add(function() { 15655 c.destroy(); 15656 }); 15657 15658 // Fix for bug #1897785, #1898007 15659 if (tinymce.isIE) { 15660 c.onShowMenu.add(function() { 15661 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15662 ed.focus(); 15663 15664 bm = ed.selection.getBookmark(1); 15665 }); 15666 15667 c.onHideMenu.add(function() { 15668 if (bm) { 15669 ed.selection.moveToBookmark(bm); 15670 bm = 0; 15671 } 15672 }); 15673 } 15674 15675 return t.add(c); 15676 }, 15677 15678 createListBox : function(id, s, cc) { 15679 var t = this, ed = t.editor, cmd, c, cls; 15680 15681 if (t.get(id)) 15682 return null; 15683 15684 s.title = ed.translate(s.title); 15685 s.scope = s.scope || ed; 15686 15687 if (!s.onselect) { 15688 s.onselect = function(v) { 15689 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15690 }; 15691 } 15692 15693 s = extend({ 15694 title : s.title, 15695 'class' : 'mce_' + id, 15696 scope : s.scope, 15697 control_manager : t 15698 }, s); 15699 15700 id = t.prefix + id; 15701 15702 15703 function useNativeListForAccessibility(ed) { 15704 return ed.settings.use_accessible_selects && !tinymce.isGecko 15705 } 15706 15707 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 15708 c = new tinymce.ui.NativeListBox(id, s); 15709 else { 15710 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 15711 c = new cls(id, s, ed); 15712 } 15713 15714 t.controls[id] = c; 15715 15716 // Fix focus problem in Safari 15717 if (tinymce.isWebKit) { 15718 c.onPostRender.add(function(c, n) { 15719 // Store bookmark on mousedown 15720 Event.add(n, 'mousedown', function() { 15721 ed.bookmark = ed.selection.getBookmark(1); 15722 }); 15723 15724 // Restore on focus, since it might be lost 15725 Event.add(n, 'focus', function() { 15726 ed.selection.moveToBookmark(ed.bookmark); 15727 ed.bookmark = null; 15728 }); 15729 }); 15730 } 15731 15732 if (c.hideMenu) 15733 ed.onMouseDown.add(c.hideMenu, c); 15734 15735 return t.add(c); 15736 }, 15737 15738 createButton : function(id, s, cc) { 15739 var t = this, ed = t.editor, o, c, cls; 15740 15741 if (t.get(id)) 15742 return null; 15743 15744 s.title = ed.translate(s.title); 15745 s.label = ed.translate(s.label); 15746 s.scope = s.scope || ed; 15747 15748 if (!s.onclick && !s.menu_button) { 15749 s.onclick = function() { 15750 ed.execCommand(s.cmd, s.ui || false, s.value); 15751 }; 15752 } 15753 15754 s = extend({ 15755 title : s.title, 15756 'class' : 'mce_' + id, 15757 unavailable_prefix : ed.getLang('unavailable', ''), 15758 scope : s.scope, 15759 control_manager : t 15760 }, s); 15761 15762 id = t.prefix + id; 15763 15764 if (s.menu_button) { 15765 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 15766 c = new cls(id, s, ed); 15767 ed.onMouseDown.add(c.hideMenu, c); 15768 } else { 15769 cls = t._cls.button || tinymce.ui.Button; 15770 c = new cls(id, s, ed); 15771 } 15772 15773 return t.add(c); 15774 }, 15775 15776 createMenuButton : function(id, s, cc) { 15777 s = s || {}; 15778 s.menu_button = 1; 15779 15780 return this.createButton(id, s, cc); 15781 }, 15782 15783 createSplitButton : function(id, s, cc) { 15784 var t = this, ed = t.editor, cmd, c, cls; 15785 15786 if (t.get(id)) 15787 return null; 15788 15789 s.title = ed.translate(s.title); 15790 s.scope = s.scope || ed; 15791 15792 if (!s.onclick) { 15793 s.onclick = function(v) { 15794 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15795 }; 15796 } 15797 15798 if (!s.onselect) { 15799 s.onselect = function(v) { 15800 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15801 }; 15802 } 15803 15804 s = extend({ 15805 title : s.title, 15806 'class' : 'mce_' + id, 15807 scope : s.scope, 15808 control_manager : t 15809 }, s); 15810 15811 id = t.prefix + id; 15812 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 15813 c = t.add(new cls(id, s, ed)); 15814 ed.onMouseDown.add(c.hideMenu, c); 15815 15816 return c; 15817 }, 15818 15819 createColorSplitButton : function(id, s, cc) { 15820 var t = this, ed = t.editor, cmd, c, cls, bm; 15821 15822 if (t.get(id)) 15823 return null; 15824 15825 s.title = ed.translate(s.title); 15826 s.scope = s.scope || ed; 15827 15828 if (!s.onclick) { 15829 s.onclick = function(v) { 15830 if (tinymce.isIE) 15831 bm = ed.selection.getBookmark(1); 15832 15833 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15834 }; 15835 } 15836 15837 if (!s.onselect) { 15838 s.onselect = function(v) { 15839 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15840 }; 15841 } 15842 15843 s = extend({ 15844 title : s.title, 15845 'class' : 'mce_' + id, 15846 'menu_class' : ed.getParam('skin') + 'Skin', 15847 scope : s.scope, 15848 more_colors_title : ed.getLang('more_colors') 15849 }, s); 15850 15851 id = t.prefix + id; 15852 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 15853 c = new cls(id, s, ed); 15854 ed.onMouseDown.add(c.hideMenu, c); 15855 15856 // Remove the menu element when the editor is removed 15857 ed.onRemove.add(function() { 15858 c.destroy(); 15859 }); 15860 15861 // Fix for bug #1897785, #1898007 15862 if (tinymce.isIE) { 15863 c.onShowMenu.add(function() { 15864 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15865 ed.focus(); 15866 bm = ed.selection.getBookmark(1); 15867 }); 15868 15869 c.onHideMenu.add(function() { 15870 if (bm) { 15871 ed.selection.moveToBookmark(bm); 15872 bm = 0; 15873 } 15874 }); 15875 } 15876 15877 return t.add(c); 15878 }, 15879 15880 createToolbar : function(id, s, cc) { 15881 var c, t = this, cls; 15882 15883 id = t.prefix + id; 15884 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 15885 c = new cls(id, s, t.editor); 15886 15887 if (t.get(id)) 15888 return null; 15889 15890 return t.add(c); 15891 }, 15892 15893 createToolbarGroup : function(id, s, cc) { 15894 var c, t = this, cls; 15895 id = t.prefix + id; 15896 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 15897 c = new cls(id, s, t.editor); 15898 15899 if (t.get(id)) 15900 return null; 15901 15902 return t.add(c); 15903 }, 15904 15905 createSeparator : function(cc) { 15906 var cls = cc || this._cls.separator || tinymce.ui.Separator; 15907 15908 return new cls(); 15909 }, 15910 15911 setControlType : function(n, c) { 15912 return this._cls[n.toLowerCase()] = c; 15913 }, 15914 15915 destroy : function() { 15916 each(this.controls, function(c) { 15917 c.destroy(); 15918 }); 15919 15920 this.controls = null; 15921 } 15922 }); 15923 })(tinymce); 15924 15925 (function(tinymce) { 15926 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 15927 15928 tinymce.create('tinymce.WindowManager', { 15929 WindowManager : function(ed) { 15930 var t = this; 15931 15932 t.editor = ed; 15933 t.onOpen = new Dispatcher(t); 15934 t.onClose = new Dispatcher(t); 15935 t.params = {}; 15936 t.features = {}; 15937 }, 15938 15939 open : function(s, p) { 15940 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 15941 15942 // Default some options 15943 s = s || {}; 15944 p = p || {}; 15945 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 15946 sh = isOpera ? vp.h : screen.height; 15947 s.name = s.name || 'mc_' + new Date().getTime(); 15948 s.width = parseInt(s.width || 320); 15949 s.height = parseInt(s.height || 240); 15950 s.resizable = true; 15951 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 15952 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 15953 p.inline = false; 15954 p.mce_width = s.width; 15955 p.mce_height = s.height; 15956 p.mce_auto_focus = s.auto_focus; 15957 15958 if (mo) { 15959 if (isIE) { 15960 s.center = true; 15961 s.help = false; 15962 s.dialogWidth = s.width + 'px'; 15963 s.dialogHeight = s.height + 'px'; 15964 s.scroll = s.scrollbars || false; 15965 } 15966 } 15967 15968 // Build features string 15969 each(s, function(v, k) { 15970 if (tinymce.is(v, 'boolean')) 15971 v = v ? 'yes' : 'no'; 15972 15973 if (!/^(name|url)$/.test(k)) { 15974 if (isIE && mo) 15975 f += (f ? ';' : '') + k + ':' + v; 15976 else 15977 f += (f ? ',' : '') + k + '=' + v; 15978 } 15979 }); 15980 15981 t.features = s; 15982 t.params = p; 15983 t.onOpen.dispatch(t, s, p); 15984 15985 u = s.url || s.file; 15986 u = tinymce._addVer(u); 15987 15988 try { 15989 if (isIE && mo) { 15990 w = 1; 15991 window.showModalDialog(u, window, f); 15992 } else 15993 w = window.open(u, s.name, f); 15994 } catch (ex) { 15995 // Ignore 15996 } 15997 15998 if (!w) 15999 alert(t.editor.getLang('popup_blocked')); 16000 }, 16001 16002 close : function(w) { 16003 w.close(); 16004 this.onClose.dispatch(this); 16005 }, 16006 16007 createInstance : function(cl, a, b, c, d, e) { 16008 var f = tinymce.resolve(cl); 16009 16010 return new f(a, b, c, d, e); 16011 }, 16012 16013 confirm : function(t, cb, s, w) { 16014 w = w || window; 16015 16016 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16017 }, 16018 16019 alert : function(tx, cb, s, w) { 16020 var t = this; 16021 16022 w = w || window; 16023 w.alert(t._decode(t.editor.getLang(tx, tx))); 16024 16025 if (cb) 16026 cb.call(s || t); 16027 }, 16028 16029 resizeBy : function(dw, dh, win) { 16030 win.resizeBy(dw, dh); 16031 }, 16032 16033 // Internal functions 16034 16035 _decode : function(s) { 16036 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16037 } 16038 }); 16039 }(tinymce)); 16040 (function(tinymce) { 16041 tinymce.Formatter = function(ed) { 16042 var formats = {}, 16043 each = tinymce.each, 16044 dom = ed.dom, 16045 selection = ed.selection, 16046 TreeWalker = tinymce.dom.TreeWalker, 16047 rangeUtils = new tinymce.dom.RangeUtils(dom), 16048 isValid = ed.schema.isValidChild, 16049 isBlock = dom.isBlock, 16050 forcedRootBlock = ed.settings.forced_root_block, 16051 nodeIndex = dom.nodeIndex, 16052 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF', 16053 MCE_ATTR_RE = /^(src|href|style)$/, 16054 FALSE = false, 16055 TRUE = true, 16056 formatChangeData, 16057 undef, 16058 getContentEditable = dom.getContentEditable; 16059 16060 function isArray(obj) { 16061 return obj instanceof Array; 16062 }; 16063 16064 function getParents(node, selector) { 16065 return dom.getParents(node, selector, dom.getRoot()); 16066 }; 16067 16068 function isCaretNode(node) { 16069 return node.nodeType === 1 && node.id === '_mce_caret'; 16070 }; 16071 16072 function defaultFormats() { 16073 register({ 16074 alignleft : [ 16075 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16076 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16077 ], 16078 16079 aligncenter : [ 16080 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16081 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16082 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16083 ], 16084 16085 alignright : [ 16086 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16087 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16088 ], 16089 16090 alignfull : [ 16091 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16092 ], 16093 16094 bold : [ 16095 {inline : 'strong', remove : 'all'}, 16096 {inline : 'span', styles : {fontWeight : 'bold'}}, 16097 {inline : 'b', remove : 'all'} 16098 ], 16099 16100 italic : [ 16101 {inline : 'em', remove : 'all'}, 16102 {inline : 'span', styles : {fontStyle : 'italic'}}, 16103 {inline : 'i', remove : 'all'} 16104 ], 16105 16106 underline : [ 16107 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16108 {inline : 'u', remove : 'all'} 16109 ], 16110 16111 strikethrough : [ 16112 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16113 {inline : 'strike', remove : 'all'} 16114 ], 16115 16116 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16117 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16118 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16119 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16120 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16121 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16122 subscript : {inline : 'sub'}, 16123 superscript : {inline : 'sup'}, 16124 16125 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16126 onmatch : function(node) { 16127 return true; 16128 }, 16129 16130 onformat : function(elm, fmt, vars) { 16131 each(vars, function(value, key) { 16132 dom.setAttrib(elm, key, value); 16133 }); 16134 } 16135 }, 16136 16137 removeformat : [ 16138 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16139 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16140 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16141 ] 16142 }); 16143 16144 // Register default block formats 16145 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16146 register(name, {block : name, remove : 'all'}); 16147 }); 16148 16149 // Register user defined formats 16150 register(ed.settings.formats); 16151 }; 16152 16153 function addKeyboardShortcuts() { 16154 // Add some inline shortcuts 16155 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16156 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16157 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16158 16159 // BlockFormat shortcuts keys 16160 for (var i = 1; i <= 6; i++) { 16161 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16162 } 16163 16164 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16165 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16166 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16167 }; 16168 16169 // Public functions 16170 16171 function get(name) { 16172 return name ? formats[name] : formats; 16173 }; 16174 16175 function register(name, format) { 16176 if (name) { 16177 if (typeof(name) !== 'string') { 16178 each(name, function(format, name) { 16179 register(name, format); 16180 }); 16181 } else { 16182 // Force format into array and add it to internal collection 16183 format = format.length ? format : [format]; 16184 16185 each(format, function(format) { 16186 // Set deep to false by default on selector formats this to avoid removing 16187 // alignment on images inside paragraphs when alignment is changed on paragraphs 16188 if (format.deep === undef) 16189 format.deep = !format.selector; 16190 16191 // Default to true 16192 if (format.split === undef) 16193 format.split = !format.selector || format.inline; 16194 16195 // Default to true 16196 if (format.remove === undef && format.selector && !format.inline) 16197 format.remove = 'none'; 16198 16199 // Mark format as a mixed format inline + block level 16200 if (format.selector && format.inline) { 16201 format.mixed = true; 16202 format.block_expand = true; 16203 } 16204 16205 // Split classes if needed 16206 if (typeof(format.classes) === 'string') 16207 format.classes = format.classes.split(/\s+/); 16208 }); 16209 16210 formats[name] = format; 16211 } 16212 } 16213 }; 16214 16215 var getTextDecoration = function(node) { 16216 var decoration; 16217 16218 ed.dom.getParent(node, function(n) { 16219 decoration = ed.dom.getStyle(n, 'text-decoration'); 16220 return decoration && decoration !== 'none'; 16221 }); 16222 16223 return decoration; 16224 }; 16225 16226 var processUnderlineAndColor = function(node) { 16227 var textDecoration; 16228 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16229 textDecoration = getTextDecoration(node.parentNode); 16230 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16231 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16232 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16233 ed.dom.setStyle(node, 'text-decoration', null); 16234 } 16235 } 16236 }; 16237 16238 function apply(name, vars, node) { 16239 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16240 16241 function setElementFormat(elm, fmt) { 16242 fmt = fmt || format; 16243 16244 if (elm) { 16245 if (fmt.onformat) { 16246 fmt.onformat(elm, fmt, vars, node); 16247 } 16248 16249 each(fmt.styles, function(value, name) { 16250 dom.setStyle(elm, name, replaceVars(value, vars)); 16251 }); 16252 16253 each(fmt.attributes, function(value, name) { 16254 dom.setAttrib(elm, name, replaceVars(value, vars)); 16255 }); 16256 16257 each(fmt.classes, function(value) { 16258 value = replaceVars(value, vars); 16259 16260 if (!dom.hasClass(elm, value)) 16261 dom.addClass(elm, value); 16262 }); 16263 } 16264 }; 16265 function adjustSelectionToVisibleSelection() { 16266 function findSelectionEnd(start, end) { 16267 var walker = new TreeWalker(end); 16268 for (node = walker.current(); node; node = walker.prev()) { 16269 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16270 return node; 16271 } 16272 } 16273 }; 16274 16275 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16276 // as this isn't visible to the user. 16277 var rng = ed.selection.getRng(); 16278 var start = rng.startContainer; 16279 var end = rng.endContainer; 16280 16281 if (start != end && rng.endOffset === 0) { 16282 var newEnd = findSelectionEnd(start, end); 16283 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16284 16285 rng.setEnd(newEnd, endOffset); 16286 } 16287 16288 return rng; 16289 } 16290 16291 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16292 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16293 16294 // find the index of the first child list. 16295 each(node.childNodes, function(n, index) { 16296 if (n.nodeName === "UL" || n.nodeName === "OL") { 16297 listIndex = index; 16298 list = n; 16299 return false; 16300 } 16301 }); 16302 16303 // get the index of the bookmarks 16304 each(node.childNodes, function(n, index) { 16305 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16306 if (n.id == bookmark.id + "_start") { 16307 startIndex = index; 16308 } else if (n.id == bookmark.id + "_end") { 16309 endIndex = index; 16310 } 16311 } 16312 }); 16313 16314 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16315 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16316 each(tinymce.grep(node.childNodes), process); 16317 return 0; 16318 } else { 16319 currentWrapElm = dom.clone(wrapElm, FALSE); 16320 16321 // create a list of the nodes on the same side of the list as the selection 16322 each(tinymce.grep(node.childNodes), function(n, index) { 16323 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16324 nodes.push(n); 16325 n.parentNode.removeChild(n); 16326 } 16327 }); 16328 16329 // insert the wrapping element either before or after the list. 16330 if (startIndex < listIndex) { 16331 node.insertBefore(currentWrapElm, list); 16332 } else if (startIndex > listIndex) { 16333 node.insertBefore(currentWrapElm, list.nextSibling); 16334 } 16335 16336 // add the new nodes to the list. 16337 newWrappers.push(currentWrapElm); 16338 16339 each(nodes, function(node) { 16340 currentWrapElm.appendChild(node); 16341 }); 16342 16343 return currentWrapElm; 16344 } 16345 }; 16346 16347 function applyRngStyle(rng, bookmark, node_specific) { 16348 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16349 16350 // Setup wrapper element 16351 wrapName = format.inline || format.block; 16352 wrapElm = dom.create(wrapName); 16353 setElementFormat(wrapElm); 16354 16355 rangeUtils.walk(rng, function(nodes) { 16356 var currentWrapElm; 16357 16358 function process(node) { 16359 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16360 16361 lastContentEditable = contentEditable; 16362 nodeName = node.nodeName.toLowerCase(); 16363 parentName = node.parentNode.nodeName.toLowerCase(); 16364 16365 // Node has a contentEditable value 16366 if (node.nodeType === 1 && getContentEditable(node)) { 16367 lastContentEditable = contentEditable; 16368 contentEditable = getContentEditable(node) === "true"; 16369 hasContentEditableState = true; // We don't want to wrap the container only it's children 16370 } 16371 16372 // Stop wrapping on br elements 16373 if (isEq(nodeName, 'br')) { 16374 currentWrapElm = 0; 16375 16376 // Remove any br elements when we wrap things 16377 if (format.block) 16378 dom.remove(node); 16379 16380 return; 16381 } 16382 16383 // If node is wrapper type 16384 if (format.wrapper && matchNode(node, name, vars)) { 16385 currentWrapElm = 0; 16386 return; 16387 } 16388 16389 // Can we rename the block 16390 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16391 node = dom.rename(node, wrapName); 16392 setElementFormat(node); 16393 newWrappers.push(node); 16394 currentWrapElm = 0; 16395 return; 16396 } 16397 16398 // Handle selector patterns 16399 if (format.selector) { 16400 // Look for matching formats 16401 each(formatList, function(format) { 16402 // Check collapsed state if it exists 16403 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16404 return; 16405 } 16406 16407 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16408 setElementFormat(node, format); 16409 found = true; 16410 } 16411 }); 16412 16413 // Continue processing if a selector match wasn't found and a inline element is defined 16414 if (!format.inline || found) { 16415 currentWrapElm = 0; 16416 return; 16417 } 16418 } 16419 16420 // Is it valid to wrap this item 16421 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16422 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { 16423 // Start wrapping 16424 if (!currentWrapElm) { 16425 // Wrap the node 16426 currentWrapElm = dom.clone(wrapElm, FALSE); 16427 node.parentNode.insertBefore(currentWrapElm, node); 16428 newWrappers.push(currentWrapElm); 16429 } 16430 16431 currentWrapElm.appendChild(node); 16432 } else if (nodeName == 'li' && bookmark) { 16433 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16434 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16435 } else { 16436 // Start a new wrapper for possible children 16437 currentWrapElm = 0; 16438 16439 each(tinymce.grep(node.childNodes), process); 16440 16441 if (hasContentEditableState) { 16442 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16443 } 16444 16445 // End the last wrapper 16446 currentWrapElm = 0; 16447 } 16448 }; 16449 16450 // Process siblings from range 16451 each(nodes, process); 16452 }); 16453 16454 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16455 if (format.wrap_links === false) { 16456 each(newWrappers, function(node) { 16457 function process(node) { 16458 var i, currentWrapElm, children; 16459 16460 if (node.nodeName === 'A') { 16461 currentWrapElm = dom.clone(wrapElm, FALSE); 16462 newWrappers.push(currentWrapElm); 16463 16464 children = tinymce.grep(node.childNodes); 16465 for (i = 0; i < children.length; i++) 16466 currentWrapElm.appendChild(children[i]); 16467 16468 node.appendChild(currentWrapElm); 16469 } 16470 16471 each(tinymce.grep(node.childNodes), process); 16472 }; 16473 16474 process(node); 16475 }); 16476 } 16477 16478 // Cleanup 16479 16480 each(newWrappers, function(node) { 16481 var childCount; 16482 16483 function getChildCount(node) { 16484 var count = 0; 16485 16486 each(node.childNodes, function(node) { 16487 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16488 count++; 16489 }); 16490 16491 return count; 16492 }; 16493 16494 function mergeStyles(node) { 16495 var child, clone; 16496 16497 each(node.childNodes, function(node) { 16498 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16499 child = node; 16500 return FALSE; // break loop 16501 } 16502 }); 16503 16504 // If child was found and of the same type as the current node 16505 if (child && matchName(child, format)) { 16506 clone = dom.clone(child, FALSE); 16507 setElementFormat(clone); 16508 16509 dom.replace(clone, node, TRUE); 16510 dom.remove(child, 1); 16511 } 16512 16513 return clone || node; 16514 }; 16515 16516 childCount = getChildCount(node); 16517 16518 // Remove empty nodes but only if there is multiple wrappers and they are not block 16519 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16520 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16521 dom.remove(node, 1); 16522 return; 16523 } 16524 16525 if (format.inline || format.wrapper) { 16526 // Merges the current node with it's children of similar type to reduce the number of elements 16527 if (!format.exact && childCount === 1) 16528 node = mergeStyles(node); 16529 16530 // Remove/merge children 16531 each(formatList, function(format) { 16532 // Merge all children of similar type will move styles from child to parent 16533 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16534 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16535 each(dom.select(format.inline, node), function(child) { 16536 var parent; 16537 16538 // When wrap_links is set to false we don't want 16539 // to remove the format on children within links 16540 if (format.wrap_links === false) { 16541 parent = child.parentNode; 16542 16543 do { 16544 if (parent.nodeName === 'A') 16545 return; 16546 } while (parent = parent.parentNode); 16547 } 16548 16549 removeFormat(format, vars, child, format.exact ? child : null); 16550 }); 16551 }); 16552 16553 // Remove child if direct parent is of same type 16554 if (matchNode(node.parentNode, name, vars)) { 16555 dom.remove(node, 1); 16556 node = 0; 16557 return TRUE; 16558 } 16559 16560 // Look for parent with similar style format 16561 if (format.merge_with_parents) { 16562 dom.getParent(node.parentNode, function(parent) { 16563 if (matchNode(parent, name, vars)) { 16564 dom.remove(node, 1); 16565 node = 0; 16566 return TRUE; 16567 } 16568 }); 16569 } 16570 16571 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16572 if (node && format.merge_siblings !== false) { 16573 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16574 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16575 } 16576 } 16577 }); 16578 }; 16579 16580 if (format) { 16581 if (node) { 16582 if (node.nodeType) { 16583 rng = dom.createRng(); 16584 rng.setStartBefore(node); 16585 rng.setEndAfter(node); 16586 applyRngStyle(expandRng(rng, formatList), null, true); 16587 } else { 16588 applyRngStyle(node, null, true); 16589 } 16590 } else { 16591 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16592 // Obtain selection node before selection is unselected by applyRngStyle() 16593 var curSelNode = ed.selection.getNode(); 16594 16595 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 16596 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16597 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16598 apply(formatList[0].defaultBlock); 16599 } 16600 16601 // Apply formatting to selection 16602 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16603 bookmark = selection.getBookmark(); 16604 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 16605 16606 // Colored nodes should be underlined so that the color of the underline matches the text color. 16607 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 16608 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 16609 processUnderlineAndColor(curSelNode); 16610 } 16611 16612 selection.moveToBookmark(bookmark); 16613 moveStart(selection.getRng(TRUE)); 16614 ed.nodeChanged(); 16615 } else 16616 performCaretAction('apply', name, vars); 16617 } 16618 } 16619 }; 16620 16621 function remove(name, vars, node) { 16622 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 16623 16624 // Merges the styles for each node 16625 function process(node) { 16626 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 16627 16628 // Node has a contentEditable value 16629 if (node.nodeType === 1 && getContentEditable(node)) { 16630 lastContentEditable = contentEditable; 16631 contentEditable = getContentEditable(node) === "true"; 16632 hasContentEditableState = true; // We don't want to wrap the container only it's children 16633 } 16634 16635 // Grab the children first since the nodelist might be changed 16636 children = tinymce.grep(node.childNodes); 16637 16638 // Process current node 16639 if (contentEditable && !hasContentEditableState) { 16640 for (i = 0, l = formatList.length; i < l; i++) { 16641 if (removeFormat(formatList[i], vars, node, node)) 16642 break; 16643 } 16644 } 16645 16646 // Process the children 16647 if (format.deep) { 16648 if (children.length) { 16649 for (i = 0, l = children.length; i < l; i++) 16650 process(children[i]); 16651 16652 if (hasContentEditableState) { 16653 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16654 } 16655 } 16656 } 16657 }; 16658 16659 function findFormatRoot(container) { 16660 var formatRoot; 16661 16662 // Find format root 16663 each(getParents(container.parentNode).reverse(), function(parent) { 16664 var format; 16665 16666 // Find format root element 16667 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 16668 // Is the node matching the format we are looking for 16669 format = matchNode(parent, name, vars); 16670 if (format && format.split !== false) 16671 formatRoot = parent; 16672 } 16673 }); 16674 16675 return formatRoot; 16676 }; 16677 16678 function wrapAndSplit(format_root, container, target, split) { 16679 var parent, clone, lastClone, firstClone, i, formatRootParent; 16680 16681 // Format root found then clone formats and split it 16682 if (format_root) { 16683 formatRootParent = format_root.parentNode; 16684 16685 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 16686 clone = dom.clone(parent, FALSE); 16687 16688 for (i = 0; i < formatList.length; i++) { 16689 if (removeFormat(formatList[i], vars, clone, clone)) { 16690 clone = 0; 16691 break; 16692 } 16693 } 16694 16695 // Build wrapper node 16696 if (clone) { 16697 if (lastClone) 16698 clone.appendChild(lastClone); 16699 16700 if (!firstClone) 16701 firstClone = clone; 16702 16703 lastClone = clone; 16704 } 16705 } 16706 16707 // Never split block elements if the format is mixed 16708 if (split && (!format.mixed || !isBlock(format_root))) 16709 container = dom.split(format_root, container); 16710 16711 // Wrap container in cloned formats 16712 if (lastClone) { 16713 target.parentNode.insertBefore(lastClone, target); 16714 firstClone.appendChild(target); 16715 } 16716 } 16717 16718 return container; 16719 }; 16720 16721 function splitToFormatRoot(container) { 16722 return wrapAndSplit(findFormatRoot(container), container, container, true); 16723 }; 16724 16725 function unwrap(start) { 16726 var node = dom.get(start ? '_start' : '_end'), 16727 out = node[start ? 'firstChild' : 'lastChild']; 16728 16729 // If the end is placed within the start the result will be removed 16730 // So this checks if the out node is a bookmark node if it is it 16731 // checks for another more suitable node 16732 if (isBookmarkNode(out)) 16733 out = out[start ? 'firstChild' : 'lastChild']; 16734 16735 dom.remove(node, true); 16736 16737 return out; 16738 }; 16739 16740 function removeRngStyle(rng) { 16741 var startContainer, endContainer, node; 16742 16743 rng = expandRng(rng, formatList, TRUE); 16744 16745 if (format.split) { 16746 startContainer = getContainer(rng, TRUE); 16747 endContainer = getContainer(rng); 16748 16749 if (startContainer != endContainer) { 16750 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 16751 // This will happen if you tripple click a table cell and use remove formatting 16752 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 16753 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 16754 } 16755 16756 // Wrap start/end nodes in span element since these might be cloned/moved 16757 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 16758 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 16759 16760 // Split start/end 16761 splitToFormatRoot(startContainer); 16762 splitToFormatRoot(endContainer); 16763 16764 // Unwrap start/end to get real elements again 16765 startContainer = unwrap(TRUE); 16766 endContainer = unwrap(); 16767 } else 16768 startContainer = endContainer = splitToFormatRoot(startContainer); 16769 16770 // Update range positions since they might have changed after the split operations 16771 rng.startContainer = startContainer.parentNode; 16772 rng.startOffset = nodeIndex(startContainer); 16773 rng.endContainer = endContainer.parentNode; 16774 rng.endOffset = nodeIndex(endContainer) + 1; 16775 } 16776 16777 // Remove items between start/end 16778 rangeUtils.walk(rng, function(nodes) { 16779 each(nodes, function(node) { 16780 process(node); 16781 16782 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 16783 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 16784 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 16785 } 16786 }); 16787 }); 16788 }; 16789 16790 // Handle node 16791 if (node) { 16792 if (node.nodeType) { 16793 rng = dom.createRng(); 16794 rng.setStartBefore(node); 16795 rng.setEndAfter(node); 16796 removeRngStyle(rng); 16797 } else { 16798 removeRngStyle(node); 16799 } 16800 16801 return; 16802 } 16803 16804 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16805 bookmark = selection.getBookmark(); 16806 removeRngStyle(selection.getRng(TRUE)); 16807 selection.moveToBookmark(bookmark); 16808 16809 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node 16810 if (format.inline && match(name, vars, selection.getStart())) { 16811 moveStart(selection.getRng(true)); 16812 } 16813 16814 ed.nodeChanged(); 16815 } else 16816 performCaretAction('remove', name, vars); 16817 }; 16818 16819 function toggle(name, vars, node) { 16820 var fmt = get(name); 16821 16822 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 16823 remove(name, vars, node); 16824 else 16825 apply(name, vars, node); 16826 }; 16827 16828 function matchNode(node, name, vars, similar) { 16829 var formatList = get(name), format, i, classes; 16830 16831 function matchItems(node, format, item_name) { 16832 var key, value, items = format[item_name], i; 16833 16834 // Custom match 16835 if (format.onmatch) { 16836 return format.onmatch(node, format, item_name); 16837 } 16838 16839 // Check all items 16840 if (items) { 16841 // Non indexed object 16842 if (items.length === undef) { 16843 for (key in items) { 16844 if (items.hasOwnProperty(key)) { 16845 if (item_name === 'attributes') 16846 value = dom.getAttrib(node, key); 16847 else 16848 value = getStyle(node, key); 16849 16850 if (similar && !value && !format.exact) 16851 return; 16852 16853 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 16854 return; 16855 } 16856 } 16857 } else { 16858 // Only one match needed for indexed arrays 16859 for (i = 0; i < items.length; i++) { 16860 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 16861 return format; 16862 } 16863 } 16864 } 16865 16866 return format; 16867 }; 16868 16869 if (formatList && node) { 16870 // Check each format in list 16871 for (i = 0; i < formatList.length; i++) { 16872 format = formatList[i]; 16873 16874 // Name name, attributes, styles and classes 16875 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 16876 // Match classes 16877 if (classes = format.classes) { 16878 for (i = 0; i < classes.length; i++) { 16879 if (!dom.hasClass(node, classes[i])) 16880 return; 16881 } 16882 } 16883 16884 return format; 16885 } 16886 } 16887 } 16888 }; 16889 16890 function match(name, vars, node) { 16891 var startNode; 16892 16893 function matchParents(node) { 16894 // Find first node with similar format settings 16895 node = dom.getParent(node, function(node) { 16896 return !!matchNode(node, name, vars, true); 16897 }); 16898 16899 // Do an exact check on the similar format element 16900 return matchNode(node, name, vars); 16901 }; 16902 16903 // Check specified node 16904 if (node) 16905 return matchParents(node); 16906 16907 // Check selected node 16908 node = selection.getNode(); 16909 if (matchParents(node)) 16910 return TRUE; 16911 16912 // Check start node if it's different 16913 startNode = selection.getStart(); 16914 if (startNode != node) { 16915 if (matchParents(startNode)) 16916 return TRUE; 16917 } 16918 16919 return FALSE; 16920 }; 16921 16922 function matchAll(names, vars) { 16923 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 16924 16925 // Check start of selection for formats 16926 startElement = selection.getStart(); 16927 dom.getParent(startElement, function(node) { 16928 var i, name; 16929 16930 for (i = 0; i < names.length; i++) { 16931 name = names[i]; 16932 16933 if (!checkedMap[name] && matchNode(node, name, vars)) { 16934 checkedMap[name] = true; 16935 matchedFormatNames.push(name); 16936 } 16937 } 16938 }, dom.getRoot()); 16939 16940 return matchedFormatNames; 16941 }; 16942 16943 function canApply(name) { 16944 var formatList = get(name), startNode, parents, i, x, selector; 16945 16946 if (formatList) { 16947 startNode = selection.getStart(); 16948 parents = getParents(startNode); 16949 16950 for (x = formatList.length - 1; x >= 0; x--) { 16951 selector = formatList[x].selector; 16952 16953 // Format is not selector based, then always return TRUE 16954 if (!selector) 16955 return TRUE; 16956 16957 for (i = parents.length - 1; i >= 0; i--) { 16958 if (dom.is(parents[i], selector)) 16959 return TRUE; 16960 } 16961 } 16962 } 16963 16964 return FALSE; 16965 }; 16966 16967 function formatChanged(formats, callback) { 16968 var currentFormats; 16969 16970 // Setup format node change logic 16971 if (!formatChangeData) { 16972 formatChangeData = {}; 16973 currentFormats = {}; 16974 16975 ed.onNodeChange.addToTop(function(ed, cm, node) { 16976 var parents = getParents(node), matchedFormats = {}; 16977 16978 // Check for new formats 16979 each(formatChangeData, function(callbacks, format) { 16980 each(parents, function(node) { 16981 if (matchNode(node, format, {}, true)) { 16982 if (!currentFormats[format]) { 16983 // Execute callbacks 16984 each(callbacks, function(callback) { 16985 callback(true, {node: node, format: format, parents: parents}); 16986 }); 16987 16988 currentFormats[format] = callbacks; 16989 } 16990 16991 matchedFormats[format] = callbacks; 16992 return false; 16993 } 16994 }); 16995 }); 16996 16997 // Check if current formats still match 16998 each(currentFormats, function(callbacks, format) { 16999 if (!matchedFormats[format]) { 17000 delete currentFormats[format]; 17001 17002 each(callbacks, function(callback) { 17003 callback(false, {node: node, format: format, parents: parents}); 17004 }); 17005 } 17006 }); 17007 }); 17008 } 17009 17010 // Add format listeners 17011 each(formats.split(','), function(format) { 17012 if (!formatChangeData[format]) { 17013 formatChangeData[format] = []; 17014 } 17015 17016 formatChangeData[format].push(callback); 17017 }); 17018 17019 return this; 17020 }; 17021 17022 // Expose to public 17023 tinymce.extend(this, { 17024 get : get, 17025 register : register, 17026 apply : apply, 17027 remove : remove, 17028 toggle : toggle, 17029 match : match, 17030 matchAll : matchAll, 17031 matchNode : matchNode, 17032 canApply : canApply, 17033 formatChanged: formatChanged 17034 }); 17035 17036 // Initialize 17037 defaultFormats(); 17038 addKeyboardShortcuts(); 17039 17040 // Private functions 17041 17042 function matchName(node, format) { 17043 // Check for inline match 17044 if (isEq(node, format.inline)) 17045 return TRUE; 17046 17047 // Check for block match 17048 if (isEq(node, format.block)) 17049 return TRUE; 17050 17051 // Check for selector match 17052 if (format.selector) 17053 return dom.is(node, format.selector); 17054 }; 17055 17056 function isEq(str1, str2) { 17057 str1 = str1 || ''; 17058 str2 = str2 || ''; 17059 17060 str1 = '' + (str1.nodeName || str1); 17061 str2 = '' + (str2.nodeName || str2); 17062 17063 return str1.toLowerCase() == str2.toLowerCase(); 17064 }; 17065 17066 function getStyle(node, name) { 17067 var styleVal = dom.getStyle(node, name); 17068 17069 // Force the format to hex 17070 if (name == 'color' || name == 'backgroundColor') 17071 styleVal = dom.toHex(styleVal); 17072 17073 // Opera will return bold as 700 17074 if (name == 'fontWeight' && styleVal == 700) 17075 styleVal = 'bold'; 17076 17077 return '' + styleVal; 17078 }; 17079 17080 function replaceVars(value, vars) { 17081 if (typeof(value) != "string") 17082 value = value(vars); 17083 else if (vars) { 17084 value = value.replace(/%(\w+)/g, function(str, name) { 17085 return vars[name] || str; 17086 }); 17087 } 17088 17089 return value; 17090 }; 17091 17092 function isWhiteSpaceNode(node) { 17093 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17094 }; 17095 17096 function wrap(node, name, attrs) { 17097 var wrapper = dom.create(name, attrs); 17098 17099 node.parentNode.insertBefore(wrapper, node); 17100 wrapper.appendChild(node); 17101 17102 return wrapper; 17103 }; 17104 17105 function expandRng(rng, format, remove) { 17106 var sibling, lastIdx, leaf, endPoint, 17107 startContainer = rng.startContainer, 17108 startOffset = rng.startOffset, 17109 endContainer = rng.endContainer, 17110 endOffset = rng.endOffset; 17111 17112 // This function walks up the tree if there is no siblings before/after the node 17113 function findParentContainer(start) { 17114 var container, parent, child, sibling, siblingName, root; 17115 17116 container = parent = start ? startContainer : endContainer; 17117 siblingName = start ? 'previousSibling' : 'nextSibling'; 17118 root = dom.getRoot(); 17119 17120 // If it's a text node and the offset is inside the text 17121 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17122 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17123 return container; 17124 } 17125 } 17126 17127 for (;;) { 17128 // Stop expanding on block elements 17129 if (!format[0].block_expand && isBlock(parent)) 17130 return parent; 17131 17132 // Walk left/right 17133 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17134 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { 17135 return parent; 17136 } 17137 } 17138 17139 // Check if we can move up are we at root level or body level 17140 if (parent.parentNode == root) { 17141 container = parent; 17142 break; 17143 } 17144 17145 parent = parent.parentNode; 17146 } 17147 17148 return container; 17149 }; 17150 17151 // This function walks down the tree to find the leaf at the selection. 17152 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17153 function findLeaf(node, offset) { 17154 if (offset === undef) 17155 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17156 while (node && node.hasChildNodes()) { 17157 node = node.childNodes[offset]; 17158 if (node) 17159 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17160 } 17161 return { node: node, offset: offset }; 17162 } 17163 17164 // If index based start position then resolve it 17165 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17166 lastIdx = startContainer.childNodes.length - 1; 17167 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17168 17169 if (startContainer.nodeType == 3) 17170 startOffset = 0; 17171 } 17172 17173 // If index based end position then resolve it 17174 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17175 lastIdx = endContainer.childNodes.length - 1; 17176 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17177 17178 if (endContainer.nodeType == 3) 17179 endOffset = endContainer.nodeValue.length; 17180 } 17181 17182 // Expands the node to the closes contentEditable false element if it exists 17183 function findParentContentEditable(node) { 17184 var parent = node; 17185 17186 while (parent) { 17187 if (parent.nodeType === 1 && getContentEditable(parent)) { 17188 return getContentEditable(parent) === "false" ? parent : node; 17189 } 17190 17191 parent = parent.parentNode; 17192 } 17193 17194 return node; 17195 }; 17196 17197 function findWordEndPoint(container, offset, start) { 17198 var walker, node, pos, lastTextNode; 17199 17200 function findSpace(node, offset) { 17201 var pos, pos2, str = node.nodeValue; 17202 17203 if (typeof(offset) == "undefined") { 17204 offset = start ? str.length : 0; 17205 } 17206 17207 if (start) { 17208 pos = str.lastIndexOf(' ', offset); 17209 pos2 = str.lastIndexOf('\u00a0', offset); 17210 pos = pos > pos2 ? pos : pos2; 17211 17212 // Include the space on remove to avoid tag soup 17213 if (pos !== -1 && !remove) { 17214 pos++; 17215 } 17216 } else { 17217 pos = str.indexOf(' ', offset); 17218 pos2 = str.indexOf('\u00a0', offset); 17219 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17220 } 17221 17222 return pos; 17223 }; 17224 17225 if (container.nodeType === 3) { 17226 pos = findSpace(container, offset); 17227 17228 if (pos !== -1) { 17229 return {container : container, offset : pos}; 17230 } 17231 17232 lastTextNode = container; 17233 } 17234 17235 // Walk the nodes inside the block 17236 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17237 while (node = walker[start ? 'prev' : 'next']()) { 17238 if (node.nodeType === 3) { 17239 lastTextNode = node; 17240 pos = findSpace(node); 17241 17242 if (pos !== -1) { 17243 return {container : node, offset : pos}; 17244 } 17245 } else if (isBlock(node)) { 17246 break; 17247 } 17248 } 17249 17250 if (lastTextNode) { 17251 if (start) { 17252 offset = 0; 17253 } else { 17254 offset = lastTextNode.length; 17255 } 17256 17257 return {container: lastTextNode, offset: offset}; 17258 } 17259 }; 17260 17261 function findSelectorEndPoint(container, sibling_name) { 17262 var parents, i, y, curFormat; 17263 17264 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17265 container = container[sibling_name]; 17266 17267 parents = getParents(container); 17268 for (i = 0; i < parents.length; i++) { 17269 for (y = 0; y < format.length; y++) { 17270 curFormat = format[y]; 17271 17272 // If collapsed state is set then skip formats that doesn't match that 17273 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17274 continue; 17275 17276 if (dom.is(parents[i], curFormat.selector)) 17277 return parents[i]; 17278 } 17279 } 17280 17281 return container; 17282 }; 17283 17284 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17285 var node; 17286 17287 // Expand to block of similar type 17288 if (!format[0].wrapper) 17289 node = dom.getParent(container, format[0].block); 17290 17291 // Expand to first wrappable block element or any block element 17292 if (!node) 17293 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); 17294 17295 // Exclude inner lists from wrapping 17296 if (node && format[0].wrapper) 17297 node = getParents(node, 'ul,ol').reverse()[0] || node; 17298 17299 // Didn't find a block element look for first/last wrappable element 17300 if (!node) { 17301 node = container; 17302 17303 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17304 node = node[sibling_name]; 17305 17306 // Break on BR but include it will be removed later on 17307 // we can't remove it now since we need to check if it can be wrapped 17308 if (isEq(node, 'br')) 17309 break; 17310 } 17311 } 17312 17313 return node || container; 17314 }; 17315 17316 // Expand to closest contentEditable element 17317 startContainer = findParentContentEditable(startContainer); 17318 endContainer = findParentContentEditable(endContainer); 17319 17320 // Exclude bookmark nodes if possible 17321 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17322 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17323 startContainer = startContainer.nextSibling || startContainer; 17324 17325 if (startContainer.nodeType == 3) 17326 startOffset = 0; 17327 } 17328 17329 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17330 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17331 endContainer = endContainer.previousSibling || endContainer; 17332 17333 if (endContainer.nodeType == 3) 17334 endOffset = endContainer.length; 17335 } 17336 17337 if (format[0].inline) { 17338 if (rng.collapsed) { 17339 // Expand left to closest word boundery 17340 endPoint = findWordEndPoint(startContainer, startOffset, true); 17341 if (endPoint) { 17342 startContainer = endPoint.container; 17343 startOffset = endPoint.offset; 17344 } 17345 17346 // Expand right to closest word boundery 17347 endPoint = findWordEndPoint(endContainer, endOffset); 17348 if (endPoint) { 17349 endContainer = endPoint.container; 17350 endOffset = endPoint.offset; 17351 } 17352 } 17353 17354 // Avoid applying formatting to a trailing space. 17355 leaf = findLeaf(endContainer, endOffset); 17356 if (leaf.node) { 17357 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17358 leaf = findLeaf(leaf.node.previousSibling); 17359 17360 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17361 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17362 17363 if (leaf.offset > 1) { 17364 endContainer = leaf.node; 17365 endContainer.splitText(leaf.offset - 1); 17366 } 17367 } 17368 } 17369 } 17370 17371 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17372 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17373 // This will reduce the number of wrapper elements that needs to be created 17374 // Move start point up the tree 17375 if (format[0].inline || format[0].block_expand) { 17376 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17377 startContainer = findParentContainer(true); 17378 } 17379 17380 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17381 endContainer = findParentContainer(); 17382 } 17383 } 17384 17385 // Expand start/end container to matching selector 17386 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17387 // Find new startContainer/endContainer if there is better one 17388 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17389 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17390 } 17391 17392 // Expand start/end container to matching block element or text node 17393 if (format[0].block || format[0].selector) { 17394 // Find new startContainer/endContainer if there is better one 17395 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17396 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17397 17398 // Non block element then try to expand up the leaf 17399 if (format[0].block) { 17400 if (!isBlock(startContainer)) 17401 startContainer = findParentContainer(true); 17402 17403 if (!isBlock(endContainer)) 17404 endContainer = findParentContainer(); 17405 } 17406 } 17407 17408 // Setup index for startContainer 17409 if (startContainer.nodeType == 1) { 17410 startOffset = nodeIndex(startContainer); 17411 startContainer = startContainer.parentNode; 17412 } 17413 17414 // Setup index for endContainer 17415 if (endContainer.nodeType == 1) { 17416 endOffset = nodeIndex(endContainer) + 1; 17417 endContainer = endContainer.parentNode; 17418 } 17419 17420 // Return new range like object 17421 return { 17422 startContainer : startContainer, 17423 startOffset : startOffset, 17424 endContainer : endContainer, 17425 endOffset : endOffset 17426 }; 17427 } 17428 17429 function removeFormat(format, vars, node, compare_node) { 17430 var i, attrs, stylesModified; 17431 17432 // Check if node matches format 17433 if (!matchName(node, format)) 17434 return FALSE; 17435 17436 // Should we compare with format attribs and styles 17437 if (format.remove != 'all') { 17438 // Remove styles 17439 each(format.styles, function(value, name) { 17440 value = replaceVars(value, vars); 17441 17442 // Indexed array 17443 if (typeof(name) === 'number') { 17444 name = value; 17445 compare_node = 0; 17446 } 17447 17448 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17449 dom.setStyle(node, name, ''); 17450 17451 stylesModified = 1; 17452 }); 17453 17454 // Remove style attribute if it's empty 17455 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17456 node.removeAttribute('style'); 17457 node.removeAttribute('data-mce-style'); 17458 } 17459 17460 // Remove attributes 17461 each(format.attributes, function(value, name) { 17462 var valueOut; 17463 17464 value = replaceVars(value, vars); 17465 17466 // Indexed array 17467 if (typeof(name) === 'number') { 17468 name = value; 17469 compare_node = 0; 17470 } 17471 17472 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17473 // Keep internal classes 17474 if (name == 'class') { 17475 value = dom.getAttrib(node, name); 17476 if (value) { 17477 // Build new class value where everything is removed except the internal prefixed classes 17478 valueOut = ''; 17479 each(value.split(/\s+/), function(cls) { 17480 if (/mce\w+/.test(cls)) 17481 valueOut += (valueOut ? ' ' : '') + cls; 17482 }); 17483 17484 // We got some internal classes left 17485 if (valueOut) { 17486 dom.setAttrib(node, name, valueOut); 17487 return; 17488 } 17489 } 17490 } 17491 17492 // IE6 has a bug where the attribute doesn't get removed correctly 17493 if (name == "class") 17494 node.removeAttribute('className'); 17495 17496 // Remove mce prefixed attributes 17497 if (MCE_ATTR_RE.test(name)) 17498 node.removeAttribute('data-mce-' + name); 17499 17500 node.removeAttribute(name); 17501 } 17502 }); 17503 17504 // Remove classes 17505 each(format.classes, function(value) { 17506 value = replaceVars(value, vars); 17507 17508 if (!compare_node || dom.hasClass(compare_node, value)) 17509 dom.removeClass(node, value); 17510 }); 17511 17512 // Check for non internal attributes 17513 attrs = dom.getAttribs(node); 17514 for (i = 0; i < attrs.length; i++) { 17515 if (attrs[i].nodeName.indexOf('_') !== 0) 17516 return FALSE; 17517 } 17518 } 17519 17520 // Remove the inline child if it's empty for example <b> or <span> 17521 if (format.remove != 'none') { 17522 removeNode(node, format); 17523 return TRUE; 17524 } 17525 }; 17526 17527 function removeNode(node, format) { 17528 var parentNode = node.parentNode, rootBlockElm; 17529 17530 function find(node, next, inc) { 17531 node = getNonWhiteSpaceSibling(node, next, inc); 17532 17533 return !node || (node.nodeName == 'BR' || isBlock(node)); 17534 }; 17535 17536 if (format.block) { 17537 if (!forcedRootBlock) { 17538 // Append BR elements if needed before we remove the block 17539 if (isBlock(node) && !isBlock(parentNode)) { 17540 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17541 node.insertBefore(dom.create('br'), node.firstChild); 17542 17543 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17544 node.appendChild(dom.create('br')); 17545 } 17546 } else { 17547 // Wrap the block in a forcedRootBlock if we are at the root of document 17548 if (parentNode == dom.getRoot()) { 17549 if (!format.list_block || !isEq(node, format.list_block)) { 17550 each(tinymce.grep(node.childNodes), function(node) { 17551 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17552 if (!rootBlockElm) 17553 rootBlockElm = wrap(node, forcedRootBlock); 17554 else 17555 rootBlockElm.appendChild(node); 17556 } else 17557 rootBlockElm = 0; 17558 }); 17559 } 17560 } 17561 } 17562 } 17563 17564 // Never remove nodes that isn't the specified inline element if a selector is specified too 17565 if (format.selector && format.inline && !isEq(format.inline, node)) 17566 return; 17567 17568 dom.remove(node, 1); 17569 }; 17570 17571 function getNonWhiteSpaceSibling(node, next, inc) { 17572 if (node) { 17573 next = next ? 'nextSibling' : 'previousSibling'; 17574 17575 for (node = inc ? node : node[next]; node; node = node[next]) { 17576 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17577 return node; 17578 } 17579 } 17580 }; 17581 17582 function isBookmarkNode(node) { 17583 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17584 }; 17585 17586 function mergeSiblings(prev, next) { 17587 var marker, sibling, tmpSibling; 17588 17589 function compareElements(node1, node2) { 17590 // Not the same name 17591 if (node1.nodeName != node2.nodeName) 17592 return FALSE; 17593 17594 function getAttribs(node) { 17595 var attribs = {}; 17596 17597 each(dom.getAttribs(node), function(attr) { 17598 var name = attr.nodeName.toLowerCase(); 17599 17600 // Don't compare internal attributes or style 17601 if (name.indexOf('_') !== 0 && name !== 'style') 17602 attribs[name] = dom.getAttrib(node, name); 17603 }); 17604 17605 return attribs; 17606 }; 17607 17608 function compareObjects(obj1, obj2) { 17609 var value, name; 17610 17611 for (name in obj1) { 17612 // Obj1 has item obj2 doesn't have 17613 if (obj1.hasOwnProperty(name)) { 17614 value = obj2[name]; 17615 17616 // Obj2 doesn't have obj1 item 17617 if (value === undef) 17618 return FALSE; 17619 17620 // Obj2 item has a different value 17621 if (obj1[name] != value) 17622 return FALSE; 17623 17624 // Delete similar value 17625 delete obj2[name]; 17626 } 17627 } 17628 17629 // Check if obj 2 has something obj 1 doesn't have 17630 for (name in obj2) { 17631 // Obj2 has item obj1 doesn't have 17632 if (obj2.hasOwnProperty(name)) 17633 return FALSE; 17634 } 17635 17636 return TRUE; 17637 }; 17638 17639 // Attribs are not the same 17640 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 17641 return FALSE; 17642 17643 // Styles are not the same 17644 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 17645 return FALSE; 17646 17647 return TRUE; 17648 }; 17649 17650 function findElementSibling(node, sibling_name) { 17651 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 17652 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 17653 return node; 17654 17655 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 17656 return sibling; 17657 } 17658 17659 return node; 17660 }; 17661 17662 // Check if next/prev exists and that they are elements 17663 if (prev && next) { 17664 // If previous sibling is empty then jump over it 17665 prev = findElementSibling(prev, 'previousSibling'); 17666 next = findElementSibling(next, 'nextSibling'); 17667 17668 // Compare next and previous nodes 17669 if (compareElements(prev, next)) { 17670 // Append nodes between 17671 for (sibling = prev.nextSibling; sibling && sibling != next;) { 17672 tmpSibling = sibling; 17673 sibling = sibling.nextSibling; 17674 prev.appendChild(tmpSibling); 17675 } 17676 17677 // Remove next node 17678 dom.remove(next); 17679 17680 // Move children into prev node 17681 each(tinymce.grep(next.childNodes), function(node) { 17682 prev.appendChild(node); 17683 }); 17684 17685 return prev; 17686 } 17687 } 17688 17689 return next; 17690 }; 17691 17692 function isTextBlock(name) { 17693 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); 17694 }; 17695 17696 function getContainer(rng, start) { 17697 var container, offset, lastIdx, walker; 17698 17699 container = rng[start ? 'startContainer' : 'endContainer']; 17700 offset = rng[start ? 'startOffset' : 'endOffset']; 17701 17702 if (container.nodeType == 1) { 17703 lastIdx = container.childNodes.length - 1; 17704 17705 if (!start && offset) 17706 offset--; 17707 17708 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 17709 } 17710 17711 // If start text node is excluded then walk to the next node 17712 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 17713 container = new TreeWalker(container, ed.getBody()).next() || container; 17714 } 17715 17716 // If end text node is excluded then walk to the previous node 17717 if (container.nodeType === 3 && !start && offset === 0) { 17718 container = new TreeWalker(container, ed.getBody()).prev() || container; 17719 } 17720 17721 return container; 17722 }; 17723 17724 function performCaretAction(type, name, vars) { 17725 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 17726 17727 // Creates a caret container bogus element 17728 function createCaretContainer(fill) { 17729 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 17730 17731 if (fill) { 17732 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 17733 } 17734 17735 return caretContainer; 17736 }; 17737 17738 function isCaretContainerEmpty(node, nodes) { 17739 while (node) { 17740 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 17741 return false; 17742 } 17743 17744 // Collect nodes 17745 if (nodes && node.nodeType === 1) { 17746 nodes.push(node); 17747 } 17748 17749 node = node.firstChild; 17750 } 17751 17752 return true; 17753 }; 17754 17755 // Returns any parent caret container element 17756 function getParentCaretContainer(node) { 17757 while (node) { 17758 if (node.id === caretContainerId) { 17759 return node; 17760 } 17761 17762 node = node.parentNode; 17763 } 17764 }; 17765 17766 // Finds the first text node in the specified node 17767 function findFirstTextNode(node) { 17768 var walker; 17769 17770 if (node) { 17771 walker = new TreeWalker(node, node); 17772 17773 for (node = walker.current(); node; node = walker.next()) { 17774 if (node.nodeType === 3) { 17775 return node; 17776 } 17777 } 17778 } 17779 }; 17780 17781 // Removes the caret container for the specified node or all on the current document 17782 function removeCaretContainer(node, move_caret) { 17783 var child, rng; 17784 17785 if (!node) { 17786 node = getParentCaretContainer(selection.getStart()); 17787 17788 if (!node) { 17789 while (node = dom.get(caretContainerId)) { 17790 removeCaretContainer(node, false); 17791 } 17792 } 17793 } else { 17794 rng = selection.getRng(true); 17795 17796 if (isCaretContainerEmpty(node)) { 17797 if (move_caret !== false) { 17798 rng.setStartBefore(node); 17799 rng.setEndBefore(node); 17800 } 17801 17802 dom.remove(node); 17803 } else { 17804 child = findFirstTextNode(node); 17805 17806 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 17807 child = child.deleteData(0, 1); 17808 } 17809 17810 dom.remove(node, 1); 17811 } 17812 17813 selection.setRng(rng); 17814 } 17815 }; 17816 17817 // Applies formatting to the caret postion 17818 function applyCaretFormat() { 17819 var rng, caretContainer, textNode, offset, bookmark, container, text; 17820 17821 rng = selection.getRng(true); 17822 offset = rng.startOffset; 17823 container = rng.startContainer; 17824 text = container.nodeValue; 17825 17826 caretContainer = getParentCaretContainer(selection.getStart()); 17827 if (caretContainer) { 17828 textNode = findFirstTextNode(caretContainer); 17829 } 17830 17831 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 17832 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 17833 // Get bookmark of caret position 17834 bookmark = selection.getBookmark(); 17835 17836 // Collapse bookmark range (WebKit) 17837 rng.collapse(true); 17838 17839 // Expand the range to the closest word and split it at those points 17840 rng = expandRng(rng, get(name)); 17841 rng = rangeUtils.split(rng); 17842 17843 // Apply the format to the range 17844 apply(name, vars, rng); 17845 17846 // Move selection back to caret position 17847 selection.moveToBookmark(bookmark); 17848 } else { 17849 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 17850 caretContainer = createCaretContainer(true); 17851 textNode = caretContainer.firstChild; 17852 17853 rng.insertNode(caretContainer); 17854 offset = 1; 17855 17856 apply(name, vars, caretContainer); 17857 } else { 17858 apply(name, vars, caretContainer); 17859 } 17860 17861 // Move selection to text node 17862 selection.setCursorLocation(textNode, offset); 17863 } 17864 }; 17865 17866 function removeCaretFormat() { 17867 var rng = selection.getRng(true), container, offset, bookmark, 17868 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 17869 17870 container = rng.startContainer; 17871 offset = rng.startOffset; 17872 node = container; 17873 17874 if (container.nodeType == 3) { 17875 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 17876 hasContentAfter = true; 17877 } 17878 17879 node = node.parentNode; 17880 } 17881 17882 while (node) { 17883 if (matchNode(node, name, vars)) { 17884 formatNode = node; 17885 break; 17886 } 17887 17888 if (node.nextSibling) { 17889 hasContentAfter = true; 17890 } 17891 17892 parents.push(node); 17893 node = node.parentNode; 17894 } 17895 17896 // Node doesn't have the specified format 17897 if (!formatNode) { 17898 return; 17899 } 17900 17901 // Is there contents after the caret then remove the format on the element 17902 if (hasContentAfter) { 17903 // Get bookmark of caret position 17904 bookmark = selection.getBookmark(); 17905 17906 // Collapse bookmark range (WebKit) 17907 rng.collapse(true); 17908 17909 // Expand the range to the closest word and split it at those points 17910 rng = expandRng(rng, get(name), true); 17911 rng = rangeUtils.split(rng); 17912 17913 // Remove the format from the range 17914 remove(name, vars, rng); 17915 17916 // Move selection back to caret position 17917 selection.moveToBookmark(bookmark); 17918 } else { 17919 caretContainer = createCaretContainer(); 17920 17921 node = caretContainer; 17922 for (i = parents.length - 1; i >= 0; i--) { 17923 node.appendChild(dom.clone(parents[i], false)); 17924 node = node.firstChild; 17925 } 17926 17927 // Insert invisible character into inner most format element 17928 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 17929 node = node.firstChild; 17930 17931 // Insert caret container after the formated node 17932 dom.insertAfter(caretContainer, formatNode); 17933 17934 // Move selection to text node 17935 selection.setCursorLocation(node, 1); 17936 } 17937 }; 17938 17939 // Checks if the parent caret container node isn't empty if that is the case it 17940 // will remove the bogus state on all children that isn't empty 17941 function unmarkBogusCaretParents() { 17942 var i, caretContainer, node; 17943 17944 caretContainer = getParentCaretContainer(selection.getStart()); 17945 if (caretContainer && !dom.isEmpty(caretContainer)) { 17946 tinymce.walk(caretContainer, function(node) { 17947 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 17948 dom.setAttrib(node, 'data-mce-bogus', null); 17949 } 17950 }, 'childNodes'); 17951 } 17952 }; 17953 17954 // Only bind the caret events once 17955 if (!self._hasCaretEvents) { 17956 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 17957 ed.onBeforeGetContent.addToTop(function() { 17958 var nodes = [], i; 17959 17960 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 17961 // Mark children 17962 i = nodes.length; 17963 while (i--) { 17964 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 17965 } 17966 } 17967 }); 17968 17969 // Remove caret container on mouse up and on key up 17970 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 17971 ed[name].addToTop(function() { 17972 removeCaretContainer(); 17973 unmarkBogusCaretParents(); 17974 }); 17975 }); 17976 17977 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 17978 ed.onKeyDown.addToTop(function(ed, e) { 17979 var keyCode = e.keyCode; 17980 17981 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 17982 removeCaretContainer(getParentCaretContainer(selection.getStart())); 17983 } 17984 17985 unmarkBogusCaretParents(); 17986 }); 17987 17988 // Remove bogus state if they got filled by contents using editor.selection.setContent 17989 selection.onSetContent.add(unmarkBogusCaretParents); 17990 17991 self._hasCaretEvents = true; 17992 } 17993 17994 // Do apply or remove caret format 17995 if (type == "apply") { 17996 applyCaretFormat(); 17997 } else { 17998 removeCaretFormat(); 17999 } 18000 }; 18001 18002 function moveStart(rng) { 18003 var container = rng.startContainer, 18004 offset = rng.startOffset, isAtEndOfText, 18005 walker, node, nodes, tmpNode; 18006 18007 // Convert text node into index if possible 18008 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18009 // Get the parent container location and walk from there 18010 offset = nodeIndex(container); 18011 container = container.parentNode; 18012 isAtEndOfText = true; 18013 } 18014 18015 // Move startContainer/startOffset in to a suitable node 18016 if (container.nodeType == 1) { 18017 nodes = container.childNodes; 18018 container = nodes[Math.min(offset, nodes.length - 1)]; 18019 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18020 18021 // If offset is at end of the parent node walk to the next one 18022 if (offset > nodes.length - 1 || isAtEndOfText) 18023 walker.next(); 18024 18025 for (node = walker.current(); node; node = walker.next()) { 18026 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18027 // IE has a "neat" feature where it moves the start node into the closest element 18028 // we can avoid this by inserting an element before it and then remove it after we set the selection 18029 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18030 node.parentNode.insertBefore(tmpNode, node); 18031 18032 // Set selection and remove tmpNode 18033 rng.setStart(node, 0); 18034 selection.setRng(rng); 18035 dom.remove(tmpNode); 18036 18037 return; 18038 } 18039 } 18040 } 18041 }; 18042 }; 18043 })(tinymce); 18044 18045 tinymce.onAddEditor.add(function(tinymce, ed) { 18046 var filters, fontSizes, dom, settings = ed.settings; 18047 18048 function replaceWithSpan(node, styles) { 18049 tinymce.each(styles, function(value, name) { 18050 if (value) 18051 dom.setStyle(node, name, value); 18052 }); 18053 18054 dom.rename(node, 'span'); 18055 }; 18056 18057 function convert(editor, params) { 18058 dom = editor.dom; 18059 18060 if (settings.convert_fonts_to_spans) { 18061 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18062 filters[node.nodeName.toLowerCase()](ed.dom, node); 18063 }); 18064 } 18065 }; 18066 18067 if (settings.inline_styles) { 18068 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18069 18070 filters = { 18071 font : function(dom, node) { 18072 replaceWithSpan(node, { 18073 backgroundColor : node.style.backgroundColor, 18074 color : node.color, 18075 fontFamily : node.face, 18076 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18077 }); 18078 }, 18079 18080 u : function(dom, node) { 18081 replaceWithSpan(node, { 18082 textDecoration : 'underline' 18083 }); 18084 }, 18085 18086 strike : function(dom, node) { 18087 replaceWithSpan(node, { 18088 textDecoration : 'line-through' 18089 }); 18090 } 18091 }; 18092 18093 ed.onPreProcess.add(convert); 18094 ed.onSetContent.add(convert); 18095 18096 ed.onInit.add(function() { 18097 ed.selection.onSetContent.add(convert); 18098 }); 18099 } 18100 }); 18101 18102 (function(tinymce) { 18103 var TreeWalker = tinymce.dom.TreeWalker; 18104 18105 tinymce.EnterKey = function(editor) { 18106 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18107 18108 function handleEnterKey(evt) { 18109 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, 18110 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18111 18112 // Returns true if the block can be split into two blocks or not 18113 function canSplitBlock(node) { 18114 return node && 18115 dom.isBlock(node) && 18116 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18117 !/^(fixed|absolute)/i.test(node.style.position) && 18118 dom.getContentEditable(node) !== "true"; 18119 }; 18120 18121 // Renders empty block on IE 18122 function renderBlockOnIE(block) { 18123 var oldRng; 18124 18125 if (tinymce.isIE && dom.isBlock(block)) { 18126 oldRng = selection.getRng(); 18127 block.appendChild(dom.create('span', null, '\u00a0')); 18128 selection.select(block); 18129 block.lastChild.outerHTML = ''; 18130 selection.setRng(oldRng); 18131 } 18132 }; 18133 18134 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18135 function trimInlineElementsOnLeftSideOfBlock(block) { 18136 var node = block, firstChilds = [], i; 18137 18138 // Find inner most first child ex: <p><i><b>*</b></i></p> 18139 while (node = node.firstChild) { 18140 if (dom.isBlock(node)) { 18141 return; 18142 } 18143 18144 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18145 firstChilds.push(node); 18146 } 18147 } 18148 18149 i = firstChilds.length; 18150 while (i--) { 18151 node = firstChilds[i]; 18152 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18153 dom.remove(node); 18154 } 18155 } 18156 }; 18157 18158 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18159 function moveToCaretPosition(root) { 18160 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18161 18162 rng = dom.createRng(); 18163 18164 if (root.hasChildNodes()) { 18165 walker = new TreeWalker(root, root); 18166 18167 while (node = walker.current()) { 18168 if (node.nodeType == 3) { 18169 rng.setStart(node, 0); 18170 rng.setEnd(node, 0); 18171 break; 18172 } 18173 18174 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18175 rng.setStartBefore(node); 18176 rng.setEndBefore(node); 18177 break; 18178 } 18179 18180 lastNode = node; 18181 node = walker.next(); 18182 } 18183 18184 if (!node) { 18185 rng.setStart(lastNode, 0); 18186 rng.setEnd(lastNode, 0); 18187 } 18188 } else { 18189 if (root.nodeName == 'BR') { 18190 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18191 // Trick on older IE versions to render the caret before the BR between two lists 18192 if (!documentMode || documentMode < 9) { 18193 tempElm = dom.create('br'); 18194 root.parentNode.insertBefore(tempElm, root); 18195 } 18196 18197 rng.setStartBefore(root); 18198 rng.setEndBefore(root); 18199 } else { 18200 rng.setStartAfter(root); 18201 rng.setEndAfter(root); 18202 } 18203 } else { 18204 rng.setStart(root, 0); 18205 rng.setEnd(root, 0); 18206 } 18207 } 18208 18209 selection.setRng(rng); 18210 18211 // Remove tempElm created for old IE:s 18212 dom.remove(tempElm); 18213 18214 viewPort = dom.getViewPort(editor.getWin()); 18215 18216 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs 18217 y = dom.getPos(root).y; 18218 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18219 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18220 } 18221 }; 18222 18223 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18224 // This function will also copy any text formatting from the parent block and add it to the new one 18225 function createNewBlock(name) { 18226 var node = container, block, clonedNode, caretNode; 18227 18228 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18229 caretNode = block; 18230 18231 // Clone any parent styles 18232 if (settings.keep_styles !== false) { 18233 do { 18234 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18235 clonedNode = node.cloneNode(false); 18236 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18237 18238 if (block.hasChildNodes()) { 18239 clonedNode.appendChild(block.firstChild); 18240 block.appendChild(clonedNode); 18241 } else { 18242 caretNode = clonedNode; 18243 block.appendChild(clonedNode); 18244 } 18245 } 18246 } while (node = node.parentNode); 18247 } 18248 18249 // BR is needed in empty blocks on non IE browsers 18250 if (!tinymce.isIE) { 18251 caretNode.innerHTML = '<br>'; 18252 } 18253 18254 return block; 18255 }; 18256 18257 // Returns true/false if the caret is at the start/end of the parent block element 18258 function isCaretAtStartOrEndOfBlock(start) { 18259 var walker, node, name; 18260 18261 // Caret is in the middle of a text node like "a|b" 18262 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18263 return false; 18264 } 18265 18266 // If after the last element in block node edge case for #5091 18267 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18268 return true; 18269 } 18270 18271 // If the caret if before the first element in parentBlock 18272 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18273 return true; 18274 } 18275 18276 // Caret can be before/after a table 18277 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18278 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18279 } 18280 18281 // Walk the DOM and look for text nodes or non empty elements 18282 walker = new TreeWalker(container, parentBlock); 18283 18284 // If caret is in beginning or end of a text block then jump to the next/previous node 18285 if (container.nodeType == 3) { 18286 if (start && offset == 0) { 18287 walker.prev(); 18288 } else if (!start && offset == container.nodeValue.length) { 18289 walker.next(); 18290 } 18291 } 18292 18293 while (node = walker.current()) { 18294 if (node.nodeType === 1) { 18295 // Ignore bogus elements 18296 if (!node.getAttribute('data-mce-bogus')) { 18297 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18298 name = node.nodeName.toLowerCase(); 18299 if (nonEmptyElementsMap[name] && name !== 'br') { 18300 return false; 18301 } 18302 } 18303 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18304 return false; 18305 } 18306 18307 if (start) { 18308 walker.prev(); 18309 } else { 18310 walker.next(); 18311 } 18312 } 18313 18314 return true; 18315 }; 18316 18317 // Wraps any text nodes or inline elements in the specified forced root block name 18318 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18319 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18320 18321 // Not in a block element or in a table cell or caption 18322 parentBlock = dom.getParent(container, dom.isBlock); 18323 if (!parentBlock || !canSplitBlock(parentBlock)) { 18324 parentBlock = parentBlock || editableRoot; 18325 18326 if (!parentBlock.hasChildNodes()) { 18327 newBlock = dom.create(blockName); 18328 parentBlock.appendChild(newBlock); 18329 rng.setStart(newBlock, 0); 18330 rng.setEnd(newBlock, 0); 18331 return newBlock; 18332 } 18333 18334 // Find parent that is the first child of parentBlock 18335 node = container; 18336 while (node.parentNode != parentBlock) { 18337 node = node.parentNode; 18338 } 18339 18340 // Loop left to find start node start wrapping at 18341 while (node && !dom.isBlock(node)) { 18342 startNode = node; 18343 node = node.previousSibling; 18344 } 18345 18346 if (startNode) { 18347 newBlock = dom.create(blockName); 18348 startNode.parentNode.insertBefore(newBlock, startNode); 18349 18350 // Start wrapping until we hit a block 18351 node = startNode; 18352 while (node && !dom.isBlock(node)) { 18353 next = node.nextSibling; 18354 newBlock.appendChild(node); 18355 node = next; 18356 } 18357 18358 // Restore range to it's past location 18359 rng.setStart(container, offset); 18360 rng.setEnd(container, offset); 18361 } 18362 } 18363 18364 return container; 18365 }; 18366 18367 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18368 function handleEmptyListItem() { 18369 function isFirstOrLastLi(first) { 18370 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18371 18372 // Find first/last element since there might be whitespace there 18373 while (node) { 18374 if (node.nodeType == 1) { 18375 break; 18376 } 18377 18378 node = node[first ? 'nextSibling' : 'previousSibling']; 18379 } 18380 18381 return node === parentBlock; 18382 }; 18383 18384 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18385 18386 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18387 // Is first and last list item then replace the OL/UL with a text block 18388 dom.replace(newBlock, containerBlock); 18389 } else if (isFirstOrLastLi(true)) { 18390 // First LI in list then remove LI and add text block before list 18391 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18392 } else if (isFirstOrLastLi()) { 18393 // Last LI in list then temove LI and add text block after list 18394 dom.insertAfter(newBlock, containerBlock); 18395 renderBlockOnIE(newBlock); 18396 } else { 18397 // Middle LI in list the split the list and insert a text block in the middle 18398 // Extract after fragment and insert it after the current block 18399 tmpRng = rng.cloneRange(); 18400 tmpRng.setStartAfter(parentBlock); 18401 tmpRng.setEndAfter(containerBlock); 18402 fragment = tmpRng.extractContents(); 18403 dom.insertAfter(fragment, containerBlock); 18404 dom.insertAfter(newBlock, containerBlock); 18405 } 18406 18407 dom.remove(parentBlock); 18408 moveToCaretPosition(newBlock); 18409 undoManager.add(); 18410 }; 18411 18412 // Walks the parent block to the right and look for BR elements 18413 function hasRightSideBr() { 18414 var walker = new TreeWalker(container, parentBlock), node; 18415 18416 while (node = walker.current()) { 18417 if (node.nodeName == 'BR') { 18418 return true; 18419 } 18420 18421 node = walker.next(); 18422 } 18423 } 18424 18425 // Inserts a BR element if the forced_root_block option is set to false or empty string 18426 function insertBr() { 18427 var brElm, extraBr; 18428 18429 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18430 // Insert extra BR element at the end block elements 18431 if (!tinymce.isIE && !hasRightSideBr()) { 18432 brElm = dom.create('br') 18433 rng.insertNode(brElm); 18434 rng.setStartAfter(brElm); 18435 rng.setEndAfter(brElm); 18436 extraBr = true; 18437 } 18438 } 18439 18440 brElm = dom.create('br'); 18441 rng.insertNode(brElm); 18442 18443 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18444 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18445 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18446 } 18447 18448 if (!extraBr) { 18449 rng.setStartAfter(brElm); 18450 rng.setEndAfter(brElm); 18451 } else { 18452 rng.setStartBefore(brElm); 18453 rng.setEndBefore(brElm); 18454 } 18455 18456 selection.setRng(rng); 18457 undoManager.add(); 18458 }; 18459 18460 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18461 function trimLeadingLineBreaks(node) { 18462 do { 18463 if (node.nodeType === 3) { 18464 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18465 } 18466 18467 node = node.firstChild; 18468 } while (node); 18469 }; 18470 18471 function getEditableRoot(node) { 18472 var root = dom.getRoot(), parent, editableRoot; 18473 18474 // Get all parents until we hit a non editable parent or the root 18475 parent = node; 18476 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18477 if (dom.getContentEditable(parent) === "true") { 18478 editableRoot = parent; 18479 } 18480 18481 parent = parent.parentNode; 18482 } 18483 18484 return parent !== root ? editableRoot : root; 18485 }; 18486 18487 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18488 function addBrToBlockIfNeeded(block) { 18489 var lastChild; 18490 18491 // IE will render the blocks correctly other browsers needs a BR 18492 if (!tinymce.isIE) { 18493 block.normalize(); // Remove empty text nodes that got left behind by the extract 18494 18495 // Check if the block is empty or contains a floated last child 18496 lastChild = block.lastChild; 18497 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18498 dom.add(block, 'br'); 18499 } 18500 } 18501 }; 18502 18503 // Delete any selected contents 18504 if (!rng.collapsed) { 18505 editor.execCommand('Delete'); 18506 return; 18507 } 18508 18509 // Event is blocked by some other handler for example the lists plugin 18510 if (evt.isDefaultPrevented()) { 18511 return; 18512 } 18513 18514 // Setup range items and newBlockName 18515 container = rng.startContainer; 18516 offset = rng.startOffset; 18517 newBlockName = settings.forced_root_block; 18518 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18519 documentMode = dom.doc.documentMode; 18520 18521 // Resolve node index 18522 if (container.nodeType == 1 && container.hasChildNodes()) { 18523 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18524 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18525 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18526 offset = container.nodeValue.length; 18527 } else { 18528 offset = 0; 18529 } 18530 } 18531 18532 // Get editable root node normaly the body element but sometimes a div or span 18533 editableRoot = getEditableRoot(container); 18534 18535 // If there is no editable root then enter is done inside a contentEditable false element 18536 if (!editableRoot) { 18537 return; 18538 } 18539 18540 undoManager.beforeChange(); 18541 18542 // If editable root isn't block nor the root of the editor 18543 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18544 if (!newBlockName || evt.shiftKey) { 18545 insertBr(); 18546 } 18547 18548 return; 18549 } 18550 18551 // Wrap the current node and it's sibling in a default block if it's needed. 18552 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18553 // This won't happen if root blocks are disabled or the shiftKey is pressed 18554 if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) { 18555 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18556 } 18557 18558 // Find parent block and setup empty block paddings 18559 parentBlock = dom.getParent(container, dom.isBlock); 18560 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18561 18562 // Setup block names 18563 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18564 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18565 18566 // Handle enter inside an empty list item 18567 if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) { 18568 // Let the list plugin or browser handle nested lists for now 18569 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 18570 return false; 18571 } 18572 18573 handleEmptyListItem(); 18574 return; 18575 } 18576 18577 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 18578 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 18579 if (!evt.shiftKey) { 18580 insertBr(); 18581 return; 18582 } 18583 } else { 18584 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 18585 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) { 18586 insertBr(); 18587 return; 18588 } 18589 } 18590 18591 // Default block name if it's not configured 18592 newBlockName = newBlockName || 'P'; 18593 18594 // Insert new block before/after the parent block depending on caret location 18595 if (isCaretAtStartOrEndOfBlock()) { 18596 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 18597 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 18598 newBlock = createNewBlock(newBlockName); 18599 } else { 18600 newBlock = createNewBlock(); 18601 } 18602 18603 // Split the current container block element if enter is pressed inside an empty inner block element 18604 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 18605 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 18606 newBlock = dom.split(containerBlock, parentBlock); 18607 } else { 18608 dom.insertAfter(newBlock, parentBlock); 18609 } 18610 18611 moveToCaretPosition(newBlock); 18612 } else if (isCaretAtStartOrEndOfBlock(true)) { 18613 // Insert new block before 18614 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 18615 renderBlockOnIE(newBlock); 18616 } else { 18617 // Extract after fragment and insert it after the current block 18618 tmpRng = rng.cloneRange(); 18619 tmpRng.setEndAfter(parentBlock); 18620 fragment = tmpRng.extractContents(); 18621 trimLeadingLineBreaks(fragment); 18622 newBlock = fragment.firstChild; 18623 dom.insertAfter(fragment, parentBlock); 18624 trimInlineElementsOnLeftSideOfBlock(newBlock); 18625 addBrToBlockIfNeeded(parentBlock); 18626 moveToCaretPosition(newBlock); 18627 } 18628 18629 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 18630 undoManager.add(); 18631 } 18632 18633 editor.onKeyDown.add(function(ed, evt) { 18634 if (evt.keyCode == 13) { 18635 if (handleEnterKey(evt) !== false) { 18636 evt.preventDefault(); 18637 } 18638 } 18639 }); 18640 }; 18641 })(tinymce); 18642 18643